<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Attilio Carotenuto</title>
    <description>The latest articles on DEV Community by Attilio Carotenuto (@attiliohimeki).</description>
    <link>https://dev.to/attiliohimeki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1079792%2F1a71874a-db0c-4046-91b0-7dd234d58b8d.jpg</url>
      <title>DEV Community: Attilio Carotenuto</title>
      <link>https://dev.to/attiliohimeki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/attiliohimeki"/>
    <language>en</language>
    <item>
      <title>Analysing crash dumps</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Thu, 26 Mar 2026 16:04:46 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/analysing-crash-dumps-2oo3</link>
      <guid>https://dev.to/attiliohimeki/analysing-crash-dumps-2oo3</guid>
      <description>&lt;p&gt;Crash dumps, also referred to as memory dumps or core dumps, are diagnostic files containing a variety of information about a program state that help developers identify and troubleshoot bugs, crashes and general issues.&lt;/p&gt;

&lt;p&gt;Their name and content can vary depending on the information they include, and the state of the program while they were captured. Memory dumps, despite the name, are not limited to memory, but normally include registers, stacks, threads information, callstacks and more. Core dumps are normally taken when a single process crashes, while Full Memory dump, or System Dump, captures everything in the target scope, such as full virtual memory of the process. Minidumps, on the other hand, contain a smaller subset of information, enough to do stack trace analysis. These names are often used interchangeably, and each operating system calls them a bit differently.&lt;/p&gt;

&lt;p&gt;While they are referred to as crash dumps, as they are often taken when an application crashes, they can be taken at any moment during the application lifetime. Memory dumps taken while the application is still running are often called Live Dumps. Spin dumps, on the other hand, are used to investigate hangs and performance issues, and contain data sampled over an interval of time, often while the game is still running or unresponsive.&lt;/p&gt;

&lt;p&gt;There are also considerations regarding filesize, as crash dumps are often shared from external users or automatically uploaded to a dashboard. Minidumps are relatively small and are normally used for this reason, while Core dumps and Memory dumps can be very large, often containing multiple GBs of data.&lt;/p&gt;

&lt;p&gt;Crash Dumps are a useful tool to understand why a crash happened and fix it, but they can be intimidating to use at first. Let’s see how we can generate and analyse them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Retrieving a Crash Dump when your game crashes
&lt;/h1&gt;

&lt;p&gt;Whenever your game crashes, Unity will create a crash report in the %TMP%\CompanyName\ProductName\Crashes directory, containing the Player logs, and a minidump file, which contains registers, stack, and portions of memory. If the crash originated from within the editor, it will be stored in the %TMP%\Unity\Editor\Crashes directory on Windows.&lt;/p&gt;

&lt;p&gt;Similarly, Unreal Engine will store crash reports, including dump files, in the C:\Users[username]\AppData\Local[Project Name]\Saved\Crashes directory, or in the project’s Saved/Crashes folder if the crash originated in the editor.&lt;/p&gt;

&lt;p&gt;As all developers can attest, making your game crash is not particularly hard. Still, most game engines have ways to simulate crashes in a deterministic and safe way, for testing purposes.&lt;/p&gt;

&lt;p&gt;In Unity, you can manually cause a crash by calling UnityEngine.Diagnostics.Utils.ForceCrash and specifying the crash category (AccessViolation, FatalError, Abort, or PureVirtualFunction). Calling this while in Editor Play Mode will cause the whole editor to crash, so make sure to put some safeguards around it and always save your work before testing.&lt;/p&gt;

&lt;p&gt;While working in Unreal Engine, you can explicitly cause a crash by using one of the Asserts functions, such as check(false) or checkNoEntry(), or by calling UE_LOG with Fatal verbosity. &lt;/p&gt;

&lt;h1&gt;
  
  
  Generating a memory dump on Windows
&lt;/h1&gt;

&lt;p&gt;By default, Windows does not create a memory dump when an application crashes (unless it does that explicitly, such as with Unity and Unreal). We can change this by setting up Windows Error Reporting. To do so, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Registry Editor&lt;/li&gt;
&lt;li&gt;Go to the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps&lt;/li&gt;
&lt;li&gt;Create, or edit if existing, the DumpFolder value (using Expandable String value, or REG_EXPAND_SZ), and set its value to a directory where you want to store your dumps.&lt;/li&gt;
&lt;li&gt;Create a DumpType value (as REG_DWORD), to define what memory dump type you’d like to generate. By default, Windows will generate Mini dumps if this is not set. Possible values are:
0: Custom dump
1: Mini dump
2: Full dump&lt;/li&gt;
&lt;li&gt;Optionally create a DumpCount key (as REG_DWORD), and set how many memory dumps you’d like to store. When this limit is exceeded, older files are overwritten. By default, Windows will store 10 files if the key is not set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74d4bpk9lqg534clns8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74d4bpk9lqg534clns8m.png" alt="Registry Editor" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also set specific settings for each application, by creating a new specific registry key for your application, for example HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\CrashTest.exe. Windows will use the global values first, and then override them with these specific values, if found.&lt;/p&gt;

&lt;p&gt;You can generate a memory dump while an application is still running, or hanging, by opening the Windows Task Manager, right clicking on the related process, and selecting “Create Memory Dump File”. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2c4x0b4kelula2cn5cms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2c4x0b4kelula2cn5cms.png" alt="Windows Task Manager" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will contain all application memory, so the resulting file can be quite large. These are also called user-mode memory dumps as they contain data for a specific process, rather than the whole system (kernel memory dumps).&lt;/p&gt;

&lt;p&gt;Other alternative tools for generating and analysing crash dumps on Windows are &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump" rel="noopener noreferrer"&gt;dotnet-dump&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procdump" rel="noopener noreferrer"&gt;ProcDump&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Generating a Memory Dump on Mac
&lt;/h1&gt;

&lt;p&gt;On Mac, crash dumps are also disabled by default (as on Windows, unless the application does that explicitly, such as with Unity and Unreal). In order to generate dump files when an application crashes, you first need to run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ulimit -c unlimited&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then set the coredump filename pattern. For example, this will store them in the tmp/cores folder and append the executable name, the process ID, and the UNIX timestamp to the filename:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kernel.core_pattern=/tmp/cores/core.%e.%p.%t&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Crash dumps are quite large, so you can quickly run out of disk space. You can disable them back, when you are done debugging, by running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ulimit -c 0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Whenever your game crashes, a new entry will be created in the Crash Reports section of the Console app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvjlurweuz6tduscbgh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvjlurweuz6tduscbgh4.png" alt="Mac Console App" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You also have the option to create a so-called Spindump. They are mainly used to investigate unresponsive apps, and typically contain multiple stack traces collected over ~10 seconds for one or all processes. They are generated automatically whenever you Force Quit an application that was unresponsive, and stored in the Spin Reports section of the Console app.&lt;/p&gt;

&lt;p&gt;You can manually create a spindump file by opening the Activity Monitor, looking for your game process, then opening the More menu at the top and selecting Spindump. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq49levy1c0didgu6uz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq49levy1c0didgu6uz1.png" alt="Activity Monitor" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All report files are stored in the Library/Logs/DiagnosticReports folder.&lt;/p&gt;

&lt;h1&gt;
  
  
  Analysing crash dumps with WinDbg
&lt;/h1&gt;

&lt;p&gt;While on Windows, you can analyze dump files using WinDbg. You can find it &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can then open a dump file by going to File &amp;gt; Open Dump File. &lt;/p&gt;

&lt;p&gt;As mentioned earlier, dump files can contain different amounts and types of information, depending on how they are generated. The command window will inform you about the kind of memory dump file you loaded, what it contains,and allows you to run commands to query it.&lt;/p&gt;

&lt;p&gt;After loading the dump file, WinDbg will do some preliminary checks, including what it contains, whether it has a breakpoint exception stored in it, and some metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Loading Dump File [C:\Users\attiliocarotenuto\AppData\Local\Temp\DefaultCompany\CrashTest\Crashes\Crash_2025-08-19_082728710\crash.dmp]
User Mini Dump File: Only registers, stack and portions of memory are available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First thing you want to do is run the sympath command, and pass your game directory, so WinDbg can access the relevant DLL and symbol files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.sympath+ "C:\Users\attiliocarotenuto\Desktop\CrashTest"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Whenever you change these paths, make sure to run .reload so WinDbg can load newly found symbol files.&lt;/p&gt;

&lt;p&gt;Then, as recommended by the initial prompt, you want to run the analyze command, in verbose mode:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;!analyze -v&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will show the crash reason, which module initiated the crash, and the stack trace if available.&lt;/p&gt;

&lt;p&gt;If the application did not crash but was hanging, you can add the -hang modifier to the analyze command to run specific analysis, such as whether threads are blocking other threads. Make sure to switch to the thread that you believe caused the issue beforehand, from the Threads tab.&lt;/p&gt;

&lt;p&gt;You can run the “k” command to show the stacktrace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;0:000&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;k
&lt;span class="go"&gt;  *** Stack trace for last set context - .thread/.cxr resets it
&lt;/span&gt;&lt;span class="gp"&gt; #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Child-SP          RetAddr               Call Site
&lt;span class="go"&gt;00 000000ef`db90d800 00007ffb`a8451af9     UnityPlayer!DebugStringToFilePostprocessedStacktrace+0xb0e
01 000000ef`db90d8f0 00007ffb`a74baca4     UnityPlayer!DebugStringToFile+0x6d9
02 000000ef`db90dad0 00007ffb`a6687033     UnityPlayer!DiagnosticsUtils_Bindings::ForceCrash+0xb4
03 000000ef`db90db70 000002ae`30eb5340     UnityPlayer!Utils_CUSTOM_ForceCrash+0x83
04 000000ef`db90dc10 00000000`00000000     0x000002ae`30eb5340
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want information about a specific type, you can use the “dt {type}” command.&lt;/p&gt;

&lt;p&gt;If you are interested in the modules, you can use the “lm” command to show the loaded modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;start             end                 module name
00000000`00400000 00000000`0041e000   xinput1_3   (deferred)             
000002ae`32690000 000002ae`326a6000   umppc20403   (deferred)             
000002ae`326d0000 000002ae`326e4000   CsXumd64_20403   (deferred)             
00007ff7`bee80000 00007ff7`bef29000   CrashTest   (deferred)             
00007ffb`a5b40000 00007ffb`a6583000   mono_2_0_bdwgc   (deferred)             
00007ffb`a6590000 00007ffb`aa0d4000   UnityPlayer   (pdb symbols)          C:\ProgramData\Dbg\sym\UnityPlayer_Win64_player_development_mono_x64.pdb\09C8D9C2027E45AAB4CDC87B30D02D651\UnityPlayer_Win64_player_development_mono_x64.pdb
00007ffc`87b80000 00007ffc`87bf8000   lib_burst_generated   (deferred)            
[...]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can optionally run “lm” in verbose mode by adding the “v” modifier, then it will print additional information for each loaded module, such as the Mapped memory image file, its size, version, checksum and so on.&lt;/p&gt;

&lt;p&gt;On the bottom right, you can switch to the Stack tab and analyse the callstack that caused the crash.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9t7e5kz0r0yw8sennpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9t7e5kz0r0yw8sennpb.png" alt="Stack" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t see readable names here, make sure you set up the source and symbol files correctly, as covered earlier.&lt;/p&gt;

&lt;p&gt;You can print all threads by running the ~ command, including their state.&lt;/p&gt;

&lt;h1&gt;
  
  
  Analysing crash dumps with ProcDump
&lt;/h1&gt;

&lt;p&gt;ProcDump is another free utility from Microsoft, available on Windows and Linux, that lets you generate and analyse crash dumps. It is part of SysInternal, a collection of diagnostic utilities. You can find it here: &lt;a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procdump" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/sysinternals/downloads/procdump&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cool thing that differentiates it from WinDbd is that you can define specific triggers, such as high CPU consumption or memory usage, and generate dump files when those are reached.&lt;/p&gt;

&lt;p&gt;It is a command line utility, so you’ll need to work with it from the terminal. &lt;/p&gt;

&lt;p&gt;To create a dump file, first specify what kind of content you need, and then pass either the process name or the PID. The main options are:&lt;/p&gt;

&lt;p&gt;-mm: Minidump&lt;br&gt;
-ma: Full dump&lt;br&gt;
-mt: Triage dump, which includes directly referenced memory (stacks), and limited metadata (Process, Thread, Module and Handle).&lt;/p&gt;

&lt;p&gt;By default it will generate a minidump if you don’t specify a dump type. You can also pass multiple types, if you want to generate different dump files at once.&lt;/p&gt;

&lt;p&gt;To generate a Full dump of your running game, you can run&lt;/p&gt;

&lt;p&gt;procdump -ma [processname]&lt;/p&gt;

&lt;p&gt;Or by process ID:&lt;/p&gt;

&lt;p&gt;procdump -ma [PID]&lt;/p&gt;

&lt;p&gt;As mentioned before, we can also generate dump files when certain events happen. When using triggers, you can use the -n argument to specify how many dump files ProcDump should generate before exiting.&lt;/p&gt;

&lt;p&gt;You can trigger a crash dump write when a process hangs for more than 5 seconds.&lt;/p&gt;

&lt;p&gt;procdump -ma -h [hangprocess]&lt;/p&gt;

&lt;p&gt;Or when the memory usage exceed a certain MB threshold:&lt;/p&gt;

&lt;p&gt;procdump -ma -n [amountOfDumpsToGenerate] -m [memoryThresholdMB] [processname]&lt;/p&gt;

&lt;p&gt;This command will generate up to 3 crash dumps, when the CPU consumption exceeds 20% for 5 seconds.&lt;/p&gt;

&lt;p&gt;procdump -n 3 -s 5 -c 20 [processname]&lt;/p&gt;

&lt;p&gt;You can create more complex triggers, such as when a certain message appears in the logs, when a DLL is loaded or unloaded, or when a certain exception is thrown. Make sure to check the documentation to see all possible triggers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Analysing crash dumps with Visual Studio 2022
&lt;/h1&gt;

&lt;p&gt;While debugging with Visual Studio, you can generate a dump file whenever the debugger stops at a breakpoint or exception, by selecting Debug &amp;gt; Save Dump As. This is particularly convenient when you want to resume the debugging session later on, or share it with other developers.&lt;/p&gt;

&lt;p&gt;You can choose to save the file with heaps, with variables values and loaded native modules binaries included, which makes it easier to debug, especially when you later don’t have the app binary. Alternatively, you can save without heaps, which will result in a smaller file, but will only contain stack variables values, and it will require loading the app binaries to connect symbols.&lt;/p&gt;

&lt;p&gt;You can only generate a dump file when debugging a native process, the option will be disabled when looking at managed code, for example while using the Unity debugger rather than native debugger. &lt;/p&gt;

&lt;p&gt;To load a crash dump you can select Debug &amp;gt; Debug Dump File, or drag the dump file directly into the Visual Studio window. It will then show a summary of its content which, depending on the type of dumpfile you selected, will include threads, modules, symbol paths, stack frames, and even a screenshot of the game just before it crashed.&lt;/p&gt;

&lt;p&gt;Here is a Core Dump with everything already set and included for debugging. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqzuzhsqbq2e64lb1q4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqzuzhsqbq2e64lb1q4w.png" alt="Core Dump" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, I can just press Debug from the Actions pane, which will initialise the debugger, load the executable along with symbols, and navigate directly to the relevant code that triggered the crash, along with its callstack and threads. If Visual Studio cannot find the related executable, it will prompt you to navigate and select it manually.&lt;/p&gt;

&lt;p&gt;You can open the Locals and Autos windows, from Debug &amp;gt; Windows, to inspect the value of the variables, within the current scope, when the game crashed. You can also look at raw data by opening the Memory window under Debug &amp;gt; Windows &amp;gt; Memory.&lt;/p&gt;

&lt;p&gt;To check the modules, open the related window from Debug &amp;gt; Windows. Here, Visual Studio will indicate whether the required symbols are loaded. If they are missing, you can right-click on a module, select Load Symbols, and supply the symbols file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20gaw5jl4770c5d4awk4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20gaw5jl4770c5d4awk4.png" alt="Visual Studio Symbols" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is another example with a minidump, generated by manually crashing a Unity game. As you can see, it has less data included.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4z64aumhigu8r4smzky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl4z64aumhigu8r4smzky.png" alt="Minidump" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing you want to do is to ensure symbols are correctly setup, using the “Set symbol paths” action on the right panel. You can then start a debugging session using the actions listed on the right pane.&lt;/p&gt;

&lt;h1&gt;
  
  
  Analysing crash dumps with Rider
&lt;/h1&gt;

&lt;p&gt;To inspect crash dumps in Rider, you need to create a new Run/Debug configuration, using Native Core Dump Debug as type, and set the Core dump path and the executable path. You can then set the paths where Rider should look for the related binaries, within Binaries Search Paths.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibtmrbsj1o8q02fnbu1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fibtmrbsj1o8q02fnbu1j.png" alt=" " width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, start the Debugging session. As always, the amount of information and capabilities will depend on the content of the memory dump you’ve loaded, such as callstacks, variable values and so on.&lt;/p&gt;

&lt;p&gt;Rider will load the Disassembly window, as well as the Threads &amp;amp; Variables window at the bottom, where you can inspect the call stack and variables. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq0nyyzftf6x4lbozcx5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq0nyyzftf6x4lbozcx5z.png" alt="Rider Threads &amp;amp; Variables" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Modules tab will show loaded modules, and indicate whether the related symbols are loaded. You can right click on a module and select Load Symbols to provide missing symbols, although this is only available while using the .NET debugger. Symbols can’t be loaded while the debugger is running, so they need to be set up beforehand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50a3locay3j1e544o0pf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50a3locay3j1e544o0pf.png" alt="Rider Modules" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rider has a more limited set of features compared to Visual Studio when it comes to crash dump debugging, for this reason it’s recommended to use Visual Studio if possible.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Reducing Assets Import times in Unity</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 30 Dec 2025 18:41:45 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/reducing-assets-import-times-in-unity-2kn2</link>
      <guid>https://dev.to/attiliohimeki/reducing-assets-import-times-in-unity-2kn2</guid>
      <description>&lt;p&gt;Anyone who has worked on a game has likely encountered situations where the engine repeatedly reimports assets, a seemingly unrelated change triggers a cascade of reimports, or opening a project takes an unexpectedly long time. As projects grow to include thousands of assets, these reimport cycles can dramatically increase iteration times, disrupt workflows, and slow overall team productivity.&lt;/p&gt;

&lt;p&gt;Fortunately, Unity offers a range of diagnostic tools that help you understand what’s actually happening under the hood. By using them effectively, you can reduce unnecessary reimports and spend more time building your game instead of waiting on the editor. In this tutorial, we’ll explore some of these tools and how to use them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What happens during a reimport?
&lt;/h1&gt;

&lt;p&gt;Whenever source and asset files are edited, Unity needs to reimport them and refresh its Asset Database to keep them in sync. By default, Unity performs an Asset Database refresh whenever the editor regains focus. During this process, it scans the Assets and Packages folders for any files that have been added, deleted, or modified.&lt;/p&gt;

&lt;p&gt;Unity begins by processing code-related assets, such as scripts, DLLs, and Assembly Definition files, then triggers a Domain Reload. It then proceeds to other asset types, such as textures, audio files, meshes, animation files and so on, compiling a list of all detected changes. Assets whose dependencies have changed are also added to this list and scheduled for reimport, even if the assets themselves were not directly modified.&lt;/p&gt;

&lt;p&gt;To reflect these changes in the project, Unity then runs each modified file through its importers. Since the engine typically cannot use source files in their original format, it reads them from disk, creates a metadata file describing its import settings, and generates an internal representation stored in the Library folder, in formats optimized for the editor and the target platform.&lt;/p&gt;

&lt;p&gt;The Library folder functions primarily as a local cache and is normally excluded from version control. As a result, when you open a project for the first time, or after deleting the Library folder, Unity must reimport all assets, which can be a lengthy process.&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspecting the Import Activity window
&lt;/h1&gt;

&lt;p&gt;The Import Activity window lets you see a list of the most recent imports, along with diagnostic information to understand what triggered the import, its duration, and so on. You can access it via Window &amp;gt; Analysis &amp;gt; Import Activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2omx8qf1d7amezhsp5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2omx8qf1d7amezhsp5d.png" alt="Unity Import Activity Window" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the right, the window will show a quick overview of the assets that had the longest import time, and the assets with the most dependencies. You should prioritise those to reduce your overall import time and have faster iterations in your project.&lt;/p&gt;

&lt;p&gt;On the left you’ll see a list of all the assets that have been (re)-imported. You can select one to have more information, including a list of its dependencies, and the reason why the import was triggered. This can be because no previous revision was found (so a first import, or the related artifact in the library was deleted), a change in their custom or dynamic dependencies, a Unity version upgrade resulting in an importer upgrade, or a build target change, among others. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4birsa36qv446ymxadi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4birsa36qv446ymxadi.png" alt="Asset Import Details View" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will also point you to the produced artifact in the library folder. In some cases, a single source asset will result in multiple files being generated. This is for example the case when importing .fbx models that might contain materials and animations. You can see that in the Produced Files/Artifacts pane.&lt;/p&gt;

&lt;p&gt;You can also select an asset within your Project view, right click on it and select “View in Import Activity window”, which will directly point you to its latest import event.&lt;/p&gt;

&lt;p&gt;Reimports can also happen as a result of changes to the asset dependencies, either static (such as asset name, or build target change) or dynamic (assets referencing other assets). The dependencies resolution logic was significantly overhauled from Unity 6.4 onwards, resulting in fewer dependencies and faster import times.&lt;br&gt;
You can read more about it &lt;a href="https://discussions.unity.com/t/narrowing-artifact-dependencies-in-unity-6-4/1690877" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Search Indexing Importers
&lt;/h1&gt;

&lt;p&gt;In some cases, you will see reimports in the Import Activity window where the reason looks like “ASIEI03”. These are Search Index Entry importers, used to support the Unity Search functionality. &lt;/p&gt;

&lt;p&gt;They normally are very fast and do not affect import times significantly. You can optionally disable these, from Unity 6.3 onwards, by going to Preferences &amp;gt; Search &amp;gt; Indexing and toggling Index on Editor Startup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvtr6usg55t0w8oqqtoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhvtr6usg55t0w8oqqtoa.png" alt="The Preferences &amp;gt; Indexing Window" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspecting the Editor Logs
&lt;/h1&gt;

&lt;p&gt;Whenever assets are imported, Unity will print some diagnostic info in the editor logs. This is similar to what you can see from the Activity Import window, but in text form.&lt;/p&gt;

&lt;p&gt;Here are a few examples:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Worker7] Start importing Assets/Textures/hero.png using Guid(ae35ctf46db9fd442b94a73df0ec0f44) Importer(2089858483,0ecaa5967403d0e2aa24f35e1b516d23)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Done ondemand importing asset: 'Assets/Textures/hero.png' (artifact id: '4fead12a87251aa702d2e109bfb6181b')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Done importing asset: ‘Assets/Textures/hero.png’ (target hash: ‘b6abac4492f6da0253684d7b2f48c6e3’) in 76.646241 second&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Determining why an asset is marked as changed
&lt;/h1&gt;

&lt;p&gt;Whenever an asset is modified, either externally, within the editor, or from a custom editor script, it is marked as dirty. That means that Unity will need to reimport the asset to have an accurate representation of it.&lt;/p&gt;

&lt;p&gt;While the Import Activity window provides some insights on this, with a reason for the asset reimport, in some cases it can be tricky to understand why an asset was marked as dirty. &lt;/p&gt;

&lt;p&gt;In order to do so, you can use the ObjectSetDirty Diagnostic Flag. When that is enabled, Unity will print in the Editor logs the callstack that led to the object becoming dirty.&lt;/p&gt;

&lt;p&gt;You can enable it by going to Preferences &amp;gt; Diagnostics &amp;gt; Core &amp;gt; ObjectSetDirty. Make sure to restart the Editor after toggling it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far9v76ipyuls2r96jya6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far9v76ipyuls2r96jya6.png" alt=" " width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the flag is enabled, you will start seeing relevant logs whenever an object’s dirty count is increased. They will include, in order, the dirty count, their instance ID, the object name, and its type.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Increment Dirty(3) : [-3808] Sphere (GameObject)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Similarly, when an object dirty count is cleared, normally as a result of a reimport, it will be shown in the logs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Clear Dirty(0) : [942] UIMask (Sprite)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Make sure to set Stack Trace Logging to Full for All logs, this way you’ll see the full stacktrace with a clear indication of what is setting the object as dirty. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uafjmg2pze7lpucnuc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2uafjmg2pze7lpucnuc5.png" alt="Stacktrace Logging Level" width="800" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, let’s look at these logs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Increment Dirty(1) : [17448] HeroPrefab (GameObject)&lt;br&gt;
 #0 PlatformStacktrace::GetStacktrace(int)&lt;br&gt;
 #1 DebugStringToFile(DebugStringToFileData const&amp;amp;)&lt;br&gt;
 #2 Object::IncrementPersistentDirtyIndex()&lt;br&gt;
 #3 Object::SetDirty()&lt;br&gt;
 #4 GameObject::SetLayer(int)&lt;br&gt;
 #5  (Mono JIT Code) (wrapper managed-to-native) UnityEngine.GameObject:set_layer_Injected (intptr,int)&lt;br&gt;
 #6  (Mono JIT Code) UnityEngine.GameObject:set_layer (int)&lt;br&gt;
 #7  (Mono JIT Code) [SceneModeUtility.cs:311] UnityEditor.SceneModeUtility:SetLayer (System.ReadOnlySpan&lt;/code&gt;1,int,string)&lt;br&gt;
[…]`&lt;/p&gt;

&lt;p&gt;We can determine that the asset was marked as dirty because I changed its layer.&lt;/p&gt;

&lt;p&gt;You can learn more about this diagnostic flag &lt;a href="https://discussions.unity.com/t/finding-out-what-is-setting-an-object-as-dirty/1683759" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  AssetDatabase API Operations Batching
&lt;/h1&gt;

&lt;p&gt;Database Refresh operations are often a result of Editor API calls, such as AssetDatabase.CopyAsset and AssetDatabase.DeleteAsset. By default, Unity executes each line and performs a full refresh for that Asset before moving to the next line. This is not ideal when your script modifies multiple assets, as it is often the case.&lt;/p&gt;

&lt;p&gt;To avoid this, you can call AssetDatabase.StartAssetEditing, which will suspend importing while you interact with the Asset Database, allowing you to process a batch of asset operations. Then, you call AssetDatabase.StopAssetEditing to tell Unity you are done.&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;csharp&lt;br&gt;
try&lt;br&gt;
{&lt;br&gt;
   AssetDatabase.StartAssetEditing();&lt;br&gt;
   AssetDatabase.MoveAsset("Assets/hero.png", "Assets/Textures/hero.png");&lt;br&gt;
   AssetDatabase.DeleteAsset("Assets/Old/placeholder.png");&lt;br&gt;
}&lt;br&gt;
finally&lt;br&gt;
{&lt;br&gt;
   AssetDatabase.StopAssetEditing();&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Unity keeps an internal counter for these calls. This means that you need to match the amount of StartAssetEditing and StopAssetEditing calls, otherwise the AssetDatabase will stay disabled and might become unresponsive.&lt;/p&gt;

&lt;h1&gt;
  
  
  Parallel Importing
&lt;/h1&gt;

&lt;p&gt;Since Unity 2022, you can run parallel imports for certain types of assets, significantly speeding up the import process.&lt;/p&gt;

&lt;p&gt;To enable that, go to Project Settings &amp;gt; Editor &amp;gt; Asset Pipeline and tick Parallel Import.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypw9r1sr9rctsck2rao1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypw9r1sr9rctsck2rao1.png" alt="Parallel Import Setting" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Parallel importing is supported for Textures, Models, and Audio assets. Other types of assets will still be imported sequentially.&lt;/p&gt;

&lt;p&gt;When that is enabled, Unity will spread the import workload over multiple threads. You can optionally specify how many workers you’d like Unity to use for importing, as well as setting up their standby behaviour, using the options just below. Unity will then aim to follow those settings, but the actual numbers of workers might be different depending on system availability. The default values are derived from Settings &amp;gt; Asset Pipeline &amp;gt; Import Worker Count % and are suitable for most scenarios.&lt;/p&gt;

&lt;p&gt;Parallel Import can cause unexpected issues when you have custom importers in your project, for example if they create new assets, change Editor settings, modify static variables, or if they rely on a certain execution order which might lead to race conditions. In those cases, you should aim to rewrite your importer logic to be self-contained and Thread-safe. &lt;/p&gt;

&lt;h1&gt;
  
  
  Disabling Assets Auto Refresh
&lt;/h1&gt;

&lt;p&gt;Unity refreshes the asset library whenever the editor regains focus, automatically reimporting changed assets, as well as detecting added or deleted assets. &lt;/p&gt;

&lt;p&gt;We can change this behaviour by going to Preferences &amp;gt; Asset Pipeline and setting Auto Refresh to Disabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqwhlhk652trgyfbm1wx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqwhlhk652trgyfbm1wx.png" alt="Asset Pipeline Preferences Window" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that is disabled, you will need to manually refresh the Asset Database via the Assets &amp;gt; Refresh menu, or via the related AssetDatabase.Refresh API call.&lt;/p&gt;

&lt;p&gt;Asset Database Refresh operations can also be initiated by your IDE. Rider, for example, triggers them whenever you save or add a new script file, pull from version control, or run unit tests. You can prevent Rider from requesting a refresh by going to Languages &amp;amp; Frameworks &amp;gt; Unity Engine &amp;gt; General and disabling “Automatically refresh assets in Unity”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan1jpkhrrdspytrxdcuj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan1jpkhrrdspytrxdcuj.png" alt="Rider Unity Settings Window" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Compressing Textures on Import
&lt;/h1&gt;

&lt;p&gt;Whenever you open a new project, or add new texture assets to an existing one, Unity automatically compresses those textures during the import process. This ensures that what you see in the editor is closer to what you would see in the game build. Compressing textures can take a considerable amount of time though, particularly in large projects with a lot of assets and very large image source files, resulting in very long import times.&lt;/p&gt;

&lt;p&gt;You can disable this by going to Preferences &amp;gt; Asset Pipeline &amp;gt; Compress Textures on Import and toggling it off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt0icacddhn1jab0zjqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdt0icacddhn1jab0zjqy.png" alt="Compress Textures on Import Option" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is recommended when you need to reduce long import times. When that is disabled, Unity will keep textures in an uncompressed format while working in the editor.&lt;/p&gt;

&lt;p&gt;Textures will then be compressed when making a build, resulting in longer build time. This might not be relevant if you don’t intend to make local builds, for example if you rely on a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;In some cases, you might want to disable this before you actually open the editor, for example to make the initial project import faster. You can do so by navigating to UserSettings/EditorUserSettings.asset and setting m_CompressAssetsOnImport: 0.&lt;/p&gt;

&lt;p&gt;You can read more about Texture import settings, including how to reduce their compressing time and memory footprint, &lt;a href="https://dev.to/attiliohimeki/optimising-unity-texture-import-settings-37gi"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Investigating Memory issues in Unity</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 12 Aug 2025 14:31:19 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/investigating-memory-issues-in-unity-55am</link>
      <guid>https://dev.to/attiliohimeki/investigating-memory-issues-in-unity-55am</guid>
      <description>&lt;p&gt;Memory is an important, and often overlooked, aspect of game optimisation. A game project that correctly manages memory can be the difference between a smooth experience with a high framerate, and a choppy game with frame drops.&lt;/p&gt;

&lt;p&gt;Unity uses 3 layers of memory to run your game. &lt;strong&gt;Native Memory&lt;/strong&gt;, used to run the engine itself, is normally the biggest chunk of your game memory footprint. It includes memory from the different native subsystems, such as rendering, Physics, and UI, as well as memory used for assets, scenes, and plugin buffers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managed Memory&lt;/strong&gt;, sometimes also referred to as scripting memory, is what you mainly use when writing C# game code. It comprises three parts, the Managed Heap, the Scripting Stack, and Native VM memory, and it offers a Garbage Collector to automatically allocate and release memory.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;C# Unmanaged Memory&lt;/strong&gt; layer is used as a bridge between the Native and Managed layers, allowing you to access and manipulate native memory while writing C# code. It’s typically accessed through the Unity.Collections data structures and UnsafeUtility malloc and free, and while using Burst and the Job system. Memory in this layer doesn’t use a Garbage Collector, so you’ll need to manage memory explicitly, hence the name.&lt;/p&gt;

&lt;p&gt;Let’s take some time to understand what tools the Unity Editor offers to track, investigate, and improve memory usage.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the Unity Memory Profiler
&lt;/h1&gt;

&lt;p&gt;The Unity Memory Profiler is an external package that allows you to take a snapshot of your game, as it runs, and then analyse memory usage in detail, including what assets are loaded in memory, and a breakdown of all tracked memory by type.&lt;/p&gt;

&lt;p&gt;After you take a capture, you’ll see memory divided in various categories. Let’s explain some of those terms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmttvgww74v2w490ft9dx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmttvgww74v2w490ft9dx.png" alt="Memory Profiler Capture" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the dropdown on the top you can switch between viewing Allocated memory, Resident memory, or both. &lt;strong&gt;Resident memory&lt;/strong&gt; refers to memory used by your application that actually resides on the physical memory hardware (as opposed to virtual memory). When this increases, the game will frequently experience page faults, leading to performance issues. In addition, many OS will track Resident memory usage and potentially terminate your game when it gets too high and the system needs to free up resources. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reserved memory&lt;/strong&gt; is memory that is preallocated by the Unity allocators, and generally free to be used for later allocations. This is because requesting memory from the system is relatively slow, so Unity requests large chunks in advance and keeps them available for your game.&lt;/p&gt;

&lt;p&gt;When Native memory is allocated, Unity assigns a root as its owner so it can be tracked in the Memory Profiler and in other places. In some cases, the root might be missing, or deleted without clearing up the related children. In this case, the Memory Profiler will track that in the &lt;strong&gt;Unknown&lt;/strong&gt; category, or &lt;strong&gt;Unrooted&lt;/strong&gt; in newer versions of the profiler.&lt;/p&gt;

&lt;p&gt;This is different from &lt;strong&gt;Untracked&lt;/strong&gt; memory, which is memory the game is using, as reported by the operating system, but without a clear indication of its source. Untracked Memory has not been allocated by Unity itself, instead it comes from the OS at a system level, typically from native plugins, DLLs, thread stacks, or type metadata.&lt;/p&gt;

&lt;p&gt;While you can use the Memory Profiler in the editor Play Mode, this is not recommended as the results are not representative of your game running on device. This is because a capture will include memory used by the editor itself, the editor will keep assets in memory even though your game no longer needs them, unload them at different times, and assets will be in a different format than the ones they would have on device (resulting in a different, normally larger, footprint).&lt;/p&gt;

&lt;p&gt;You can learn more about the Memory Profiler and all of its features in the &lt;a href="https://docs.unity3d.com/Packages/com.unity.memoryprofiler@1.1/manual/index.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring Native Memory Allocators
&lt;/h1&gt;

&lt;p&gt;Because requesting memory from the system is relatively slow, Unity uses a variety of allocators to reserve memory based on multiple factors, including allocation persistence, lifespan, size and so on. &lt;/p&gt;

&lt;p&gt;Unity will use the Main Thread allocator if the code is running on the main thread. If not, it will use a Thread Allocator. Then, for small allocations, it will attempt to use the Bucket Allocator (if there is space). If not, it will request it from the Dynamic Heap Allocator. The Bucket Allocator will not fragment, since allocations from it are all grouped by size. The Dynamic Heap Allocator is slower, and frequently using it for small allocations can lead to fragmentation. You can find specific technical details about each allocator, along with examples, &lt;a href="https://docs.unity3d.com/6000.1/Documentation/Manual/performance-native-memory-allocator-examples.html" rel="noopener noreferrer"&gt;in this page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep in mind that the way memory is allocated, used, and freed varies depending on what platform and architecture you’re developing on.&lt;/p&gt;

&lt;p&gt;Since Unity 2021 LTS, Unity allows you to manually configure Memory parameters to tweak your Native Memory Allocators. Allocators can be configured directly from within the editor, in Project Settings &amp;gt; Memory Settings. When you first enter the window, all values will be locked. You can click on the lock icon to make it writable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvimr2rjsw6in1dtn4d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvimr2rjsw6in1dtn4d6.png" alt="Native Allocators Settings" width="800" height="1051"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are separate settings for the Editor and for Player builds. Whenever you tweak allocator settings for the Editor, you’ll need to restart it to ensure the changes are applied. Player changes will be applied on the next build, and written directly into the boot.config file.&lt;/p&gt;

&lt;p&gt;Alternatively, you can set allocator sizes using command line arguments, either for the Editor or a game build, specifying the desired size in bytes, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-memorysetup-job-temp-allocator-block-size=2097152
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each allocator has a respective argument, you can find the full list, along with default values, &lt;a href="https://docs.unity3d.com/6000.1/Documentation/Manual/performance-native-memory-allocator-reference.html" rel="noopener noreferrer"&gt;on this page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In some cases, increasing the size of the Bucket Allocator will lead to small allocations being grouped there, improving performance and reducing fragmentation. On the other hand, this might lead to more memory being reserved for the buckets that stay unused, if your game doesn’t rely as much on small allocations.  It’s not possible to give a general rule, as it depends on a lot of different factors for your specific project.&lt;/p&gt;

&lt;p&gt;This is an advanced optimization. If you don’t know exactly what you are doing, it’s easy to negatively affect your game performance and introduce issues. For this reason, it should be kept as a last resort, and the changes should always be validated through extensive profiling, on every platform you are supporting.&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspecting Native Allocation Callstacks
&lt;/h1&gt;

&lt;p&gt;Starting with Unity 6.3, you can enable native allocation callstacks, by passing the &lt;code&gt;-enable-memoryprofiler-callstacks&lt;/code&gt; command line argument when starting the editor or your game development build.&lt;/p&gt;

&lt;p&gt;This is particularly helpful when investigating memory allocated in the Unknown section.&lt;/p&gt;

&lt;p&gt;When that is enabled, ensure “IL2CPP Stacktrace Information” is set to “Method Name, File Name, Line Number” in Project Settings &amp;gt; Player &amp;gt; Configuration.&lt;/p&gt;

&lt;p&gt;Then, take a capture via the Memory Profiler, switch to the All of Memory tab, and look for the Native &amp;gt; Native Subsystems &amp;gt; Unknown &amp;gt; Unknown group. When selecting allocations, you’ll be able to visualise the callstack, on the right hand panel, by pressing the “Call Stack Info” button.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring the Garbage Collector
&lt;/h1&gt;

&lt;p&gt;As mentioned previously, Unity handles memory in the Managed layer using a Garbage Collector. While this is convenient, it comes with performance overhead, particularly if a game frequently allocates memory that is meant to be used for a short period. This typically shows up in the Profiler as CPU spikes, and results in choppy gameplay experience with periodic stuttering.&lt;/p&gt;

&lt;p&gt;By default Unity runs garbage collection incrementally, which means the process is run over multiple frames, resulting in shorter interruptions. While this is recommended in most cases, it has some (relatively minor) added overhead, as it needs to add markers, called write barriers, to function calls that change references. &lt;/p&gt;

&lt;p&gt;Another downside of the incremental GC is that it can make it harder to spot memory issues, as the cost of the unnecessary allocations and collections will be spread over multiple frames, hiding GC spikes.&lt;/p&gt;

&lt;p&gt;You can optionally set the GC to non-incremental mode, by unchecking Project Settings &amp;gt; Player &amp;gt; Configuration &amp;gt; Use Incremental GC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmie7iq1no3n9r67ypd08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmie7iq1no3n9r67ypd08.png" alt="Incremental GC Checkbox" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that is disabled, Unity will need to stop the main thread to do a full GC sweep, often resulting in larger GC spikes. This is mainly recommended when developing non-real-time applications, or when you’re confident your title does not trigger the GC in critical sections. It’s also useful to use during development, to make GC spikes more visible during memory investigations, as mentioned earlier. In the vast majority of the cases, you want to keep Incremental GC enabled.&lt;/p&gt;

&lt;p&gt;You can also disable GC altogether by setting &lt;code&gt;GarbageCollector.GCMode&lt;/code&gt; to &lt;code&gt;GCMode.Disabled&lt;/code&gt; via scripting. In this case, the GC will not run, and calling the related collection methods will have no effect. This can be beneficial for sections where you allocate all required resources in advance and are certain that no further allocations will occur, then run for a while with GC disabled for better performance, and then re-enable it.&lt;/p&gt;

&lt;p&gt;Alternatively, you can set the GC to manual mode by setting the &lt;code&gt;GarbageCollector.GCMode&lt;/code&gt; to &lt;code&gt;GCMode.Manual&lt;/code&gt;. In this case, you’ll need to trigger it manually by calling &lt;code&gt;System.GC.Collect&lt;/code&gt; for a full blocking collection, or &lt;code&gt;GarbageCollector.CollectIncremental&lt;/code&gt; for an incremental collection pass.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the Debug Allocator
&lt;/h1&gt;

&lt;p&gt;In some cases, your game might crash due to memory corruption. This might be caused by an issue in your game scripts, a bug in the Unity engine, or both. Due to the way Unity works, the crash might be delayed during execution, making it harder to reproduce and causing loss of useful debug data.&lt;/p&gt;

&lt;p&gt;To better investigate these issues, you can add the &lt;code&gt;-debugallocator&lt;/code&gt; command line argument when running the game. When that is set, all freed or out of bounds memory accesses will cause a crash immediately. It does that by allocating a non-accessible extra page right after each allocation. The pages are decommitted but not released to the OS when the related memory is freed, again resulting in a crash if that is accessed.&lt;/p&gt;

&lt;p&gt;Setting the debugallocator flag will increase general memory usage, and memory allocation will be slower. For this reason, it’s only meant to be used for debugging sessions, and never for actual game testing or public builds.&lt;/p&gt;

&lt;p&gt;While this parameter can also be used when running the editor, it’s not recommended as the editor will become very slow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the System Allocator
&lt;/h1&gt;

&lt;p&gt;Passing the &lt;code&gt;-systemallocator&lt;/code&gt; command line argument when running your game will tell Unity to use the malloc, realloc, and free system calls, rather than Unity's Dynamic Heap Allocator.&lt;/p&gt;

&lt;p&gt;This is helpful when using Native profilers, or tools such as Application Verifier, Address Sanitizers, and Windows Performance Analyzer, as it will cause individual allocations to correctly show during profiling, rather than being lumped together.&lt;/p&gt;

&lt;p&gt;This should be used only for debugging purposes. Requesting and releasing system memory is relatively slow, while the Dynamic Heap Allocator allocates memory in pages and then holds them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Inspecting Memory Statistics
&lt;/h1&gt;

&lt;p&gt;While working in the editor or running a player build, Unity will keep track of Memory and Performance statistics. When closing it, you can have these statistics printed in the related logs. &lt;/p&gt;

&lt;p&gt;Here is an example of the first few lines of the Memory section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking for leaked weakptr:
  Found no leaked weakptrs.
Memory Statistics:
[ALLOC_TEMP_TLS] TLS Allocator
  StackAllocators : 
    [ALLOC_TEMP_CurlRequest]
      Initial Block Size 64.0 KB
      Current Block Size 64.0 KB
      Peak Allocated Bytes 0 B
      Overflow Count 0
    [ALLOC_TEMP_MAIN]
      Peak usage frame count: [1.0 MB-2.0 MB]: 1 frames, [8.0 MB-16.0 MB]: 2 frames
      Initial Block Size 16.0 MB
      Current Block Size 19.5 MB
      Peak Allocated Bytes 10.7 MB
      Overflow Count 0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These statistics can be used to understand how your game distributes its memory usage, including how many blocks are used for each allocator, peak allocation sizes, largest allocation and so on. Based on this data you can then tweak your Native Memory allocators settings, as covered earlier. Increasing the size of the bucket allocators might be a good idea for example, if you notice a lot of overflows.&lt;/p&gt;

&lt;p&gt;To enable Memory and Performance statistics in Unity 6 or newer, you’ll need to pass the &lt;code&gt;-log-memory-performance-stats&lt;/code&gt; command line argument when launching the editor or the game. &lt;/p&gt;

&lt;p&gt;In older versions of Unity, statistics are enabled by default and cannot be turned off.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced Tools for Memory Analysis
&lt;/h1&gt;

&lt;p&gt;In addition to Unity’s offering for memory profiling and management, there are other useful tools you can rely on to investigate and fix memory problems. &lt;/p&gt;

&lt;p&gt;Your first choice here should be the Native profiler for the platform you are working on, as they provide more low-level information and metrics about the underlying hardware running your game. If you are working on Android &lt;a href="https://discussions.unity.com/t/native-android-unity-profiling-using-android-studio/1641922" rel="noopener noreferrer"&gt;you can use Android Studio&lt;/a&gt;, while on iOS and MacOS &lt;a href="https://discussions.unity.com/t/native-ios-unity-profiling-using-xcode-instruments/1641923" rel="noopener noreferrer"&gt;you can rely on XCode Instruments&lt;/a&gt;. If you are developing a Windows game, &lt;a href="https://devblogs.microsoft.com/pix/introduction/" rel="noopener noreferrer"&gt;Pix is a common choice&lt;/a&gt;, while for Arm based games, including Mac with Apple Silicon chipsets, you can use &lt;a href="https://developer.arm.com/Tools%20and%20Software/Arm%20Development%20Studio" rel="noopener noreferrer"&gt;Arm Development Studio&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Application Verifier, or AppVerifier, is a runtime verification tool for unmanaged code from Microsoft that lets you debug memory corruptions, access violation issues, and more. You can read &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/application-verifier" rel="noopener noreferrer"&gt;about it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Address Sanitizer is a memory error detector from Google. It helps you track down memory leaks, Use after free/return/scope, buffer overflows, and initialization order issues. You can read &lt;a href="https://github.com/google/sanitizers/wiki/addresssanitizer" rel="noopener noreferrer"&gt;about it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Windows Performance Recorder (WPR) is a tool from Microsoft that allows you to record system and application events, that you can then analyse using the Windows Performance Analyzer (WPA). When it comes to memory, they can be used to record VirtualAlloc usage, take Heap Snapshots, track Heap and Pool usage and more. Both tools are part of the &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install" rel="noopener noreferrer"&gt;Windows Assessment and Deployment Kit (ADK)&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The Windows Performance Recorder can also be used to investigate Untracked Memory usage in Unity, as described &lt;a href="https://discussions.unity.com/t/how-can-developers-diagnose-excessive-untracked-private-ram-use/1571108/6" rel="noopener noreferrer"&gt;in this page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>optimisation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Unity Project analysis with the Project Auditor</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 03 Jun 2025 14:49:29 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/unity-project-analysis-with-the-project-auditor-2pea</link>
      <guid>https://dev.to/attiliohimeki/unity-project-analysis-with-the-project-auditor-2pea</guid>
      <description>&lt;p&gt;The Project Auditor is a static analysis tool that goes through your project to provide useful statistics, identify potential improvements, and compile a list of recommendations with just a single button click.&lt;/p&gt;

&lt;p&gt;It is a fairly recent package, made publicly available in February 2025. For this reason, not many developers are aware of it and using it. &lt;/p&gt;

&lt;p&gt;Let’s see what features the Auditor offers, and how it can help make your games run faster, as well as improving your development experience within Unity.&lt;/p&gt;

&lt;p&gt;For this tutorial, I will be using Unity 6000.0.44. Some settings and features might differ if you use a different version of the engine.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up the Auditor
&lt;/h1&gt;

&lt;p&gt;In order to start using the Auditor, open the Package Manager and install the package via name, using com.unity.project-auditor. The current version is 1.0.1.&lt;/p&gt;

&lt;p&gt;Then, open it from Window &amp;gt; Analysis &amp;gt; Project Auditor and press Start Analysis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ntdl4gny3v1lkoqhgni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ntdl4gny3v1lkoqhgni.png" alt=" " width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, the Project Auditor will cover all available areas, including Code, Project Settings, Assets, Shaders, and Build. You can tweak that in Preferences &amp;gt; Analysis &amp;gt; Project Auditor, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09v34uq9v57720mdkg9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09v34uq9v57720mdkg9j.png" alt=" " width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depending on the project size and the amount of assets and code, the analysis might take some time to complete. When ready, the main window will show a summary of the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84wuv1fqpsr5kfz0zsjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84wuv1fqpsr5kfz0zsjj.png" alt=" " width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each listed issue has a priority attached, from Major to Minor, as shown above. The summary will also conveniently show a list of the ten most significant issues in your project, those are normally the ones you want to tackle first.&lt;/p&gt;

&lt;p&gt;On the top left, the Auditor will show the Platform that was set when the Analysis was executed. If you decide to switch the active platform you’ll need to run a new analysis.&lt;/p&gt;

&lt;p&gt;Analyses are not automatically saved and will be discarded if you start a new one. To preserve the results, press the Save button on the top right corner of the window, which will generate a .projectauditor file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1yjga79i6tdrvgl90hs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1yjga79i6tdrvgl90hs.png" alt=" " width="441" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then reload it by using the Load button next to it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Improving your Code performance
&lt;/h1&gt;

&lt;p&gt;The Code section will offer various recommendations to improve the performance of your code, reducing allocations, boxing allocations, expensive method calls, and a lot more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpch16b2dkhca7zfhug15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpch16b2dkhca7zfhug15.png" alt=" " width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can select individual entries, and the Auditor will show details about the issue, as well as any eventual recommendations to fix it. You can also inspect the Inverted Call Hierarchy at the bottom, allowing you to quickly navigate to the portion of code related to the issue reported.&lt;/p&gt;

&lt;p&gt;In some cases you might want to ignore a Code issue, possibly because fixing it would require significant structural changes, or because it’s actually doing what you intended (such as explicitly allocating). In that case, you can press the Ignore Issue button and it will be hidden.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tackling Domain Reloads
&lt;/h1&gt;

&lt;p&gt;Recent versions of Unity allow you to disable Domain reloads. This significantly speeds up your iteration times, but can lead to unintended issues. For example, static variables will keep their value between sessions, static events will keep their subscribers, and non-serialized fields will preserve the values set during Play mode. In order to take advantage of this, we need to write code that correctly handles state resets. &lt;/p&gt;

&lt;p&gt;You can use the Project Auditor to analyse your code and find domain reload issues. To do so, first make sure the feature is enabled in Preferences &amp;gt; Analysis &amp;gt; Project Auditor &amp;gt; Use Roslyn Analyzers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7jgkrvukw6l4nsq1xwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7jgkrvukw6l4nsq1xwz.png" alt=" " width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, run an analysis, and within the Code section, navigate to the Domain Reload sub-section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2vklvyisqky27b3606t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2vklvyisqky27b3606t.png" alt=" " width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Auditor will provide a list of issues, along with recommendations to make the code compliant. Fixing these in most cases is fairly trivial.&lt;/p&gt;

&lt;p&gt;When you’re done, you can disable domain reloads by going to Edit &amp;gt; Project Settings &amp;gt; Editor &amp;gt; When entering Play Mode, and setting it to “Do not reload Domain or Scene”. Developing your game will be much faster and pleasant as a result.&lt;/p&gt;

&lt;h1&gt;
  
  
  Optimising your Assets
&lt;/h1&gt;

&lt;p&gt;The Assets Section offers an overview of the assets contained within your project, specifically Textures, Sprite Atlases, Meshes, Audio Clips, and various animation assets.&lt;/p&gt;

&lt;p&gt;In the Asset Issues sub-section, you’ll receive recommendations regarding Asset Import Settings, as well as warnings related to usage of the Resources folder and other best practices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd4cz4bupxoentqdc640.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd4cz4bupxoentqdc640.png" alt=" " width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with the Code section, the Auditor will show additional details and recommendations on the right panel. At the bottom it will give a quick overview of the related Asset dependencies.&lt;/p&gt;

&lt;h1&gt;
  
  
  Analysing Shaders and Shader Variants
&lt;/h1&gt;

&lt;p&gt;In the Shaders section you can get a quick overview of the Shaders and Materials contained in your project.&lt;/p&gt;

&lt;p&gt;For each Shader, the Auditor will report some useful stats such as the Number of Passes, Keywords, Properties and Texture Properties defined, and the Max amount of Variants generated from that shader. It will also show whether the shader supports GPU instancing and if it is compatible with the SRP Batcher.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2rfsjhcwtt9aq9r755w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2rfsjhcwtt9aq9r755w.png" alt=" " width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you already made a build, the Shaders view will show the amount of Built Fragment Variants for each shader.&lt;/p&gt;

&lt;p&gt;This table might not be updated if the shaders included in the latest build were already in the shader cache. If that happens, make sure to do a Clean Build and refresh the window.&lt;/p&gt;

&lt;p&gt;Switching to the Shader Variants view allows you to check all the variants included in your build, and which ones are compiled at runtime. In order to do that, you need to make a development build beforehand, ensuring that Log Shader Compilation is enabled in Project Settings &amp;gt; Graphics &amp;gt; Shader Loading (or directly from this view).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fac1b5gtcvffzk117gft5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fac1b5gtcvffzk117gft5.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You then need to run the game and move around the world, in order to trigger the relevant shader compilations. Then, you can retrieve the Player.log and drag it onto the Shader Variants view. The Compiled field will then be populated based on the info from the log file.&lt;/p&gt;

&lt;p&gt;You can learn more about this &lt;a href="https://dev.to/attiliohimeki/unity-shader-variants-optimisation-and-troubleshooting-28ci"&gt;here&lt;/a&gt;, in the “Determining which variants are used at runtime” section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Optimising your Project settings
&lt;/h1&gt;

&lt;p&gt;The Project section gives you a quick overview of the packages included in your project, as well as a list of recommendations to optimise your Project Settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhl85p27g4foizcwsb41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhl85p27g4foizcwsb41.png" alt=" " width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown in the screenshot, the Auditor will cover multiple areas, including Quality Settings, Time, Physics, Graphics and so on, as well as highlighting what platform is affected by the potential issue.&lt;/p&gt;

&lt;h1&gt;
  
  
  Producing faster and smaller Builds
&lt;/h1&gt;

&lt;p&gt;The Build section shows useful information about the latest build you made.&lt;/p&gt;

&lt;p&gt;In the Build Size sub-section, you can find useful information about the assets included in your build. Here, the Auditor gives you a breakdown of the 10 largest asset types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F368i656nenv760zxqnvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F368i656nenv760zxqnvz.png" alt=" " width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Size of Data refers to the total uncompressed size of all assets included in your build.&lt;/p&gt;

&lt;p&gt;Below that, you can find an overview of all the assets and scripts contained in your game build, as well as their size, and in which build file they’re actually included.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51jv6kkkrwej5im4pj80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51jv6kkkrwej5im4pj80.png" alt=" " width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Build Steps sub-section shows all the individual steps that were taken to create the latest build, as well as individual timings for each step. This is very helpful when dealing with long build times, allowing you to pinpoint exactly which steps are taking too long and apply the necessary optimisations. In the Information section above you can see the total time it took to complete the build, and its size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6ejxyaavre2z6j930t4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6ejxyaavre2z6j930t4.png" alt=" " width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, going back to the Code section, the Assemblies sub-section provides a quick overview of Compile Time for each assembly, as well as their dependencies. This lets you identify which areas of your code are taking the longest to compile, so you can optimise it by reducing dependencies or breaking it into smaller assemblies.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Native Android Unity Profiling using Android Studio</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Mon, 28 Apr 2025 15:02:14 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/native-android-unity-profiling-using-android-studio-1b0m</link>
      <guid>https://dev.to/attiliohimeki/native-android-unity-profiling-using-android-studio-1b0m</guid>
      <description>&lt;p&gt;While Unity offers robust profiling capabilities, in some cases it’s necessary to rely on Native profiling, offering more low-level information and metrics about the underlying hardware running your game.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll see how to do that while developing on Android, using Android Studio.&lt;/p&gt;

&lt;h1&gt;
  
  
  Preparing the build
&lt;/h1&gt;

&lt;p&gt;In order to run your game build through the Native Profiler, we need to ensure the right options are selected when generating your APK.&lt;/p&gt;

&lt;p&gt;On Android you can have Debuggable and Profileable APKs. The main difference is that Debuggable apps allow you to record more information, such as allocation and heap dumps, while Profileable apps offer more basic profiling tasks. On the other hand, debuggable APKs have significant overhead due to the added debugging capabilities, so for performance profiling, profileable APKs will provide more accurate readings.&lt;/p&gt;

&lt;p&gt;In Unity, development builds are debuggable. By selecting the “Development Build” toggle, Unity will add the android:debuggable attribute being set in the Manifest, as you can confirm by inspecting the APK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji2s9yj6big59fw7ofxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji2s9yj6big59fw7ofxg.png" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, Unity release builds are neither debuggable nor profileable. If you need a profileable build, you can export your build as a Gradle Project, and then edit the Manifest to add the relevant attribute:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;profileable android:shell="true" /&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the Player Settings, ensure the Scripting Backend dropdown is set to IL2CPP. In addition, ensure Autoconnect Profiler, Deep Profiling Support, and Script Debugging are disabled. These options are not necessary for Android Studio native profiling and have a significant impact on performance, leading to capture results that are not representative of the actual game performance.&lt;/p&gt;

&lt;p&gt;Finally, it’s useful to include debugging symbols in order to have readable callstacks. Symbols are files that attach extra information to your executable, such as function and variable names, source code filenames, line numbers, scopes, class names and so on. To do so, ensure the relevant option is selected in the Build Settings, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwose1h0j1uouqioz0qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwose1h0j1uouqioz0qn.png" alt=" " width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Public symbols provide a table that allows converting function addresses to human-readable strings, while Debugging (Full) symbols have extra information useful for debugging, but that shouldn’t be exposed to users.&lt;/p&gt;

&lt;h1&gt;
  
  
  Attaching the Android Studio Profiler
&lt;/h1&gt;

&lt;p&gt;Now that you have your APK, let’s install that on your mobile device. Then, open Android Studio to start taking a capture.&lt;/p&gt;

&lt;p&gt;If you are on Windows, you can launch the Standalone profiler in [androidstudio-installation-folder]/bin. This is not available on Mac, so you’ll need to load a project instead.&lt;/p&gt;

&lt;p&gt;From the Android Studio Launch window, select the More menu on the top-right, then “Profile or debug APK”, and navigate to your development APK.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbkoqvywlxbnm3vo3ukf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbkoqvywlxbnm3vo3ukf.png" alt=" " width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Android Studio will create a ApkProject for you, and show some general information such as the files contained within the APK.&lt;/p&gt;

&lt;p&gt;From the left menu, select the Profiler module.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsrckygx9c6jj4yz5z2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsrckygx9c6jj4yz5z2m.png" alt=" " width="692" height="931"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, ensure the correct device is selected in the top menu, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzip0b0prii4mbtfuqs1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzip0b0prii4mbtfuqs1.png" alt=" " width="678" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Profiler will now show all debuggable and profileable running processes on that device. The one related to your open project is normally marked with an Android icon, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jpm5jheqbccls77yya0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jpm5jheqbccls77yya0.png" alt=" " width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each process, the Profiler will tell you if the related configuration is Debuggable or Profileable. &lt;/p&gt;

&lt;p&gt;As you can see, the Profiler offers multiple tasks, including System traces, Heap Dumps for Memory Analysis, Callstack Samples, Allocations and so on. &lt;/p&gt;

&lt;p&gt;When starting a task, you have the option to attach it to the running process as it is, or restart it in order to capture boot time and launch metrics. Not all tasks support restarting the attached process. As mentioned before, for performance profiling is recommended to have a Profileable build as it gives more accurate readings. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgltoi93aeksaksu5yj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgltoi93aeksaksu5yj1.png" alt=" " width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to track device resources in real-time, select the “View Live Telemetry” task. Here, you can see CPU usage (both coming from your game, and other apps running on the device), optionally inspect work done by each thread, as well as memory usage. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zldhyfidr1jza4a5d4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3zldhyfidr1jza4a5d4v.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to collect more in-depth data, let’s take a System Trace instead. For this task, Android Studio won’t show data in real-time. Instead, it will keep collecting it until you manually stop the recording.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgjsrb3rjnw779g0ubfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgjsrb3rjnw779g0ubfk.png" alt=" " width="579" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let the profiler run while you play a relevant section of your game, then stop and wait until the data is parsed and the capture is being loaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygfwilpbdatmxui8248w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygfwilpbdatmxui8248w.png" alt=" " width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you can inspect CPU activity by checking the CPU Cores and Threads sections, while you can check Memory consumption in the Process Memory section.&lt;/p&gt;

&lt;p&gt;To track energy consumption, you can inspect the Power Rails section. Each hardware piece, such as CPU, GPU, Camera, WLAN, Display, and so on are shown as a separate Power Rail. The Battery section will show the related battery charge and capacity, as well as the current for each frame. Note that these two sections trace the overall device power and battery metrics, not just the one originating from your game.&lt;/p&gt;

&lt;p&gt;Finally, the Display section will show a few frame rendering metrics, such as janky frames taking longer than expected, leading to UI latency and unstable framerate.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exporting Capture Trace Files
&lt;/h1&gt;

&lt;p&gt;All the captures taken during a session will be saved in the Past Recordings tab of the Profiler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmnz6343oravkc7bmwou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmnz6343oravkc7bmwou.png" alt=" " width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These recordings will be wiped as soon as you close Android Studio. If you want to retain the data, you can use the “Export recording” button at the bottom. The capture will then be exported as a Perfetto Trace file. You can share and re-import the trace file in Android Studio by using the related “Import Recording” button. &lt;/p&gt;

&lt;p&gt;Alternatively, you can load it using Perfetto, a free Performance instrumentation and tracing web tool provided by Google. You can find it &lt;a href="https://ui.perfetto.dev/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbffjx37i0d3d4rsn1mp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbffjx37i0d3d4rsn1mp.png" alt=" " width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>android</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Optimising Unity Texture Import Settings</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 15 Apr 2025 08:25:43 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/optimising-unity-texture-import-settings-37gi</link>
      <guid>https://dev.to/attiliohimeki/optimising-unity-texture-import-settings-37gi</guid>
      <description>&lt;p&gt;In game development, textures are frequently a primary culprit for performance issues. As games get better graphics, and screens get higher resolution, this often results in high memory usage, larger build size, as well as long loading times and slowdowns. &lt;/p&gt;

&lt;p&gt;Luckily, fixing this is fairly simple, and will have a significant impact on your game. Let’s look at how we can do this in Unity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default Settings and Platform Overrides
&lt;/h2&gt;

&lt;p&gt;When textures are added to a Unity project, they go through the Texture Importer, which converts them to a format the game engine, and the user hardware, can work with.&lt;/p&gt;

&lt;p&gt;The first thing you’ll need to do is set the right type for that texture, based on how it will be used at runtime. Depending on the type you select, different settings will be available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv0rx13wqpmbifa4uf8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffv0rx13wqpmbifa4uf8g.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the Texture Importer inspector, you’ll notice a bunch of general settings, and then at the bottom a view with a Default entry, and additional tabs for each platform you have currently installed as Engine modules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuugfq0z1sdry6pbq9b7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuugfq0z1sdry6pbq9b7r.png" alt=" " width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unity allows you to define general Import Settings that apply to all platforms, and then define overrides for specific ones. While overrides are optional, it’s often worth looking into them to get the best of each platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Texture Max Size and Resizing
&lt;/h2&gt;

&lt;p&gt;The Max Size defines the maximum dimension, in pixels, for the imported texture. If the original texture is larger (either width or height), it will be downscaled. &lt;/p&gt;

&lt;p&gt;Developers often tend to overlook this value, leading to unnecessarily large textures on devices with fairly small screens, where the added details do not lead to perceivable visual quality. It might make sense to keep this value high for a full-screen image, but not as much for small icons and textures that rarely appear in-game. Tweaking this will often drastically reduce your build size and memory consumption.&lt;/p&gt;

&lt;p&gt;This value can be overridden per platform, which is handy if you want smaller textures on mobile, and larger ones on PC and consoles.&lt;/p&gt;

&lt;p&gt;The Resize Algorithm indicates the algorithm used by Unity to downscale the texture if it’s larger than the specified Max Size. The default, Mitchell, provides high quality results and should be used in most cases. The other option, Bilinear, is for cases where preserving sharp details is important, such as noise textures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compression Format
&lt;/h2&gt;

&lt;p&gt;Setting the right compression format for your game textures is vital to achieve good runtime performance, minimising runtime memory usage, loading time and CPU usage, as well as reducing your build and asset bundle size.&lt;/p&gt;

&lt;p&gt;When the Format option is set to Automatic, Unity will pick a compression format based on the Compression setting, following &lt;a href="https://docs.unity3d.com/Manual/class-TextureImporter-type-specific.html#default-formats" rel="noopener noreferrer"&gt;this table&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhlpupeqaoj8uanbn9so.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhlpupeqaoj8uanbn9so.png" alt=" " width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can override the default compression format for a specific platform, by going to Project Settings - Player - Other Settings, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwct4kytdbpvt7dmech7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwct4kytdbpvt7dmech7.png" alt=" " width="800" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, you can define specific Import settings overrides per platform, by switching to the related tab and enabling the override.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua522462ja20s3e6oau2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua522462ja20s3e6oau2.png" alt=" " width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, the Format option will offer a long list of compression formats specific for that platform. There are generally a mixture of compressed and uncompressed formats, normally indicated in the name.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Compressor Quality&lt;/strong&gt; option below is available for some specific formats, and defines how much time Unity should spend, at build time, to compress the texture. Setting it to &lt;strong&gt;Fast&lt;/strong&gt; will make your builds faster, while &lt;strong&gt;Best&lt;/strong&gt; will give you better quality at runtime, but can significantly increase your build time, especially for large textures. This option won’t have an actual impact on runtime performance, load time, and memory usage. For this reason, it’s normally a good idea to have it set to Fast during development for faster iteration times, and switch to Best for release builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the right Compression Format
&lt;/h2&gt;

&lt;p&gt;Each compression format offers a different balance between texture size, visual quality, level of detail, and support among different devices.&lt;/p&gt;

&lt;p&gt;On Mobile platforms, it’s generally recommended to use ATSC (Adaptive Scalable Texture Compression), a lossy block-based texture compression algorithm.&lt;/p&gt;

&lt;p&gt;When using ATSC, the block size determines how many bits will be stored per pixel. The lowest quality, ASTC 12×12, will store 0.89bits/pixel, while ASTC 4x4 will store 8 bits/pixel. As usual, more bits will lead to better image quality but higher build size and runtime memory usage.&lt;/p&gt;

&lt;p&gt;If you are targeting older iOS devices using the A7 chipset (such as iPad Air 1st Gen and iPad Mini 2, where the Metal graphics API was first introduced), you should select ETC or ETC2. For even older devices, you should select PVRTC.&lt;/p&gt;

&lt;p&gt;On Android, older devices with GPUs that run Vulkan or OpenGL ES 3.0 generally support the ETC2 format. Devices older than that will instead mainly rely on ETC.&lt;/p&gt;

&lt;p&gt;You can see detailed specifications about each format in &lt;a href="https://docs.unity3d.com/6000.0/Documentation/Manual/texture-formats-reference.html" rel="noopener noreferrer"&gt;this table&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While other formats, such as DXT, give comparable results to ASTC, they are not as widely supported on Mobile. As an example, the Pixel 8 does not support it, as shown in &lt;a href="https://vulkan.gpuinfo.org/displayreport.php?id=37725#formats" rel="noopener noreferrer"&gt;this page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you attempt to load a texture at runtime with an unsupported compression format for that device, Unity will decompress it using the default uncompressed format for that platform, then store the uncompressed copy in memory alongside the compressed texture. This leads to increased loading time and significantly higher runtime memory usage. &lt;/p&gt;

&lt;p&gt;You can check if the device supports a particular texture format by using &lt;strong&gt;SystemInfo.SupportsTextureFormat&lt;/strong&gt; at runtime.&lt;/p&gt;

&lt;p&gt;Some compression formats, such as ETC2 and DXT5|BC3, requires the texture’s width and height to be a multiple of 4. If the source texture doesn’t follow this rule, the texture will be kept uncompressed on disk and at runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd5pmenewqmh3jfajzlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd5pmenewqmh3jfajzlr.png" alt=" " width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can confirm this by looking at the Preview in the Inspector, where Unity will show what format it will be using instead (in most cases RGBA8 sRGB).&lt;/p&gt;

&lt;h2&gt;
  
  
  Texture Compressions Targeting
&lt;/h2&gt;

&lt;p&gt;When working with Android, you have the option to include textures compressed with multiple compressions formats in your App Bundles, by enabling Texture compressions targeting and selecting the formats in Project Settings - Player - Other Settings. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxrmwg8cqdp8mssi4jnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxrmwg8cqdp8mssi4jnn.png" alt=" " width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google Play will then deliver an optimised APK to the user with the optimal compression format for their device. This option is only available from Unity 6 onwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crunch Compression
&lt;/h2&gt;

&lt;p&gt;Crunch compression is an additional pass that runs on top of the base compression.&lt;/p&gt;

&lt;p&gt;This leads to smaller file size on disk, so it’s particularly beneficial for example when delivering assets remotely in asset bundles. Build time will increase when using this option, as compressing all textures can take some time. It also normally leads to faster loading times at runtime, but has no impact on runtime memory usage.&lt;/p&gt;

&lt;p&gt;Crunch Compression uses a lossy algorithm, which means there will be a loss in image quality relative to the reduction in file size. You can adjust the balance by using the related slider, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer4fx9a67gspfudt08hj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer4fx9a67gspfudt08hj.png" alt=" " width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not every Compression Format supports Crunch Compression. When defining a platform override, you will notice that some formats have “Crunched” in their name, meaning they will apply Crunch Compression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Read/Write
&lt;/h2&gt;

&lt;p&gt;When this option is enabled, Unity stores a copy of the texture on CPU-addressable memory, in order to allow using methods that run on the CPU, such as &lt;strong&gt;Texture2D.GetPixels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk25jzl81d43foo0qsbuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk25jzl81d43foo0qsbuy.png" alt=" " width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this doesn’t affect image size on disk, it will result in higher runtime memory usage. For this reason, it’s recommended to use it only when strictly needed. &lt;/p&gt;

&lt;p&gt;Note that methods that run on the GPU, such as &lt;strong&gt;Graphics.CopyTexture&lt;/strong&gt;, do not require Read/Write to be enabled.&lt;/p&gt;

&lt;p&gt;You can also toggle this option at runtime, to allow Unity to read the data and then free up the memory afterwards, by setting the relevant flag in &lt;strong&gt;Texture2D.Apply&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Mipmaps
&lt;/h2&gt;

&lt;p&gt;Mipmaps are smaller versions of your texture, commonly used to reduce the level of details based on the distance from the camera, speeding up rendering.&lt;/p&gt;

&lt;p&gt;They can be generated by selecting the “Generate Mipmaps” option in the Advanced settings.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg5gnt57pd3lvc209rf6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpg5gnt57pd3lvc209rf6.png" alt=" " width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling this option will increase the size of the texture by 33%, both on disk and runtime memory.&lt;/p&gt;

&lt;p&gt;While this is useful for textures that are rendered at different sizes during gameplay, it has no benefit for fixed size textures such as UI images and icons. For this reason, it’s best to keep it disabled for textures that do not benefit from different levels of details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating Physics Shape
&lt;/h2&gt;

&lt;p&gt;When dealing with Sprite textures, Unity gives you the option to generate a sprite outline, used for 2D Physics Collisions and Raycasts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkne90a753ag8ualto83a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkne90a753ag8ualto83a.png" alt=" " width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the overhead and memory footprint for this option is generally very small, it’s a good idea to disable it if the sprite does not interact with the Physics system, to gain some marginal savings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining Importer Presets
&lt;/h2&gt;

&lt;p&gt;Setting the right importer values for a large amount of assets can be quite tedious. Luckily, we can automate some of this work using the Preset Manager.&lt;/p&gt;

&lt;p&gt;To start, let’s create some presets. To do that, find a texture whose settings you want to use as a template for other assets, then click on the Preset button from the Inspector and select “Create new Preset…”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feroxjsf6mrtyo8mlb9rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feroxjsf6mrtyo8mlb9rn.png" alt=" " width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A new Importer Preset asset will be created, carrying over all the settings from the selected texture. &lt;/p&gt;

&lt;p&gt;Then, navigate to Project Settings - Preset Manager, and select Default Preset - Importer - Texture Importer. Here, you can register the preset you just created.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ujnxngff0mpnauwc1gt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ujnxngff0mpnauwc1gt.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The filter field allows you to apply the Preset only to assets in specific folders.For example, if you want to apply the preset to all textures in a Background folder, you can use this filter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glob:"Background/*.png"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In some cases you might want to apply a preset for certain values, without affecting others. To do so, you can navigate to your Importer Preset asset, then right click on the field you’d like to exclude, and select “Exclude Property”. A red marker will appear, indicating that the field won’t be overwritten by the preset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyx04ah08vg717j7cfvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyx04ah08vg717j7cfvg.png" alt=" " width="800" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you define multiple Texture Importer presets, they will be applied in order, from top to bottom, taking into account the defined filters.&lt;/p&gt;

&lt;p&gt;Note that presets will be applied whenever a new texture is imported into the project, but they will not automatically apply to existing textures, even if you reimport them. You will instead need to use the Reset option from the relevant inspector, or write some editor tooling to automate this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Determining the final texture size on build
&lt;/h2&gt;

&lt;p&gt;When you check the texture in the Inspector preview, Unity will show what resolution and format the Texture is using for the currently selected platform, and its current size while loaded in the editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkos026apsnc21tpecpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkos026apsnc21tpecpa.png" alt=" " width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The texture size reported here is just an estimate of its final size when you build the game, and does not take into account further compression and processing happening at build time, such as mipstripping.&lt;/p&gt;

&lt;p&gt;After making a build, you can go through the Build Report, in the Editor.log, to see all the textures included in your build and their size on disk. The values reported here might differ slightly from the ones shown in the Inspector, and they are a closer indication of their actual size at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build Report
Uncompressed usage by category (Percentages based on user generated assets only):
Textures            1.1 gb  93.7%
[...]
Total User Assets   1.2 gb  100.0%
Complete build size 1.3 gb
Used Assets and files from the Resources folder, sorted by uncompressed size:
[...]
512.2 kb    1.2% Assets/Scenes/Oasis/Art/Environment/Textures/BigRock_01_T_A.png
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note, the description there is a bit misleading. This includes all game assets, not just the ones from the Resources folder. Also, “Uncompressed” refers to further compression passes that the actual game build or asset bundles might go through (such as LZ4 AssetBundle compression), but the textures sizes reported here are already compressed based on the settings we previously chose, ASTC6x6 in this case, and Crunch Compression if enabled.&lt;/p&gt;

&lt;p&gt;If you are using Addressables, you can check the Addressables Report to inspect what textures are included in your build, and their size on disk, by switching to the Explore tab and viewing by Assets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6is3hx8cj0zlklrk906e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6is3hx8cj0zlklrk906e.png" alt=" " width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Determining Texture memory usage at Runtime
&lt;/h2&gt;

&lt;p&gt;The Memory Profiler is the best tool to determine what textures are loaded during gameplay, and how much memory they take. It works by manually taking captures at specific points of the game, which you can then inspect and compare afterwards.&lt;/p&gt;

&lt;p&gt;In the Summary tab, under “Top Unity Objects Categories”, you’ll see which asset category is taking the most memory, textures in this case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy00y8yt660d9otdmg2jl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy00y8yt660d9otdmg2jl.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switching to the “Unity Objects” tab will allow you to inspect what textures are loaded in memory, and how much memory they occupy in each bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tb1t84y2c9s9i5haqko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tb1t84y2c9s9i5haqko.png" alt=" " width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can toggle the “Show Potential Duplicates Only” option at the bottom, to only show textures that are duplicated in memory. This often happens when working with Asset Bundles, and solving those is an effective way to improve runtime memory usage.&lt;/p&gt;

&lt;p&gt;It’s important that you take memory captures from the game running on device. Captures taken from the editor will not accurately reflect what players will experience, as they will include assets loaded by the editor itself, as well as different compression formats and settings from the actual game build.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>console</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Managing the Android Manifest in Unity</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Thu, 20 Feb 2025 12:56:23 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/managing-the-android-manifest-in-unity-221o</link>
      <guid>https://dev.to/attiliohimeki/managing-the-android-manifest-in-unity-221o</guid>
      <description>&lt;p&gt;The Android Manifest is an XML file that describes an Android game or application, including capabilities, permissions, visual information, hardware features, entry point and so on.&lt;/p&gt;

&lt;p&gt;While each Android game has a single manifest, your Unity project will have multiple ones.  When building your game, Unity generates a final manifest by merging multiple intermediate files. &lt;/p&gt;

&lt;p&gt;In this article I will cover how Unity handles the different manifests, including troubleshooting tips and advanced modification techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the game Manifest
&lt;/h2&gt;

&lt;p&gt;When working on an Android Unity project, Unity will maintain multiple manifests. These include the Library Manifest, the Launcher Manifest, and multiple optional Plug-in Manifests.&lt;/p&gt;

&lt;p&gt;The Library Manifest, also called Main Manifest, is the base template used to generate the final manifest. It defines the entry activity, along with theme, permissions and so on.&lt;/p&gt;

&lt;p&gt;Unity provides a default one, but you can also create your own, for example if you’d like to have a custom Activity with some extra logic. In this case, you’ll need to add that in the Project Settings, under Player - Publishing Settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw0tbsk3jqqbdamob6s9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw0tbsk3jqqbdamob6s9.png" alt=" " width="662" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Launcher Manifest defines how the game looks and behaves on device, such as App Name, Icon, install location, supported Screen Density and so on. This is generated based on your Player Settings, so you normally don’t need to tweak it manually. &lt;/p&gt;

&lt;p&gt;One exception is when the project is going to be embedded into another project. In that case, you can supply a custom Launcher Manifest in the Project Settings, under Player - Publishing Settings, right below the Main Manifest.&lt;/p&gt;

&lt;p&gt;Finally, most Plug-ins and SDK will supply their own manifest, defining additional required permissions and dependencies. You normally don’t need to modify these, as they will get merged into the final manifest as part of the build process, assuming the packages are well designed and up to date. Unfortunately it’s not uncommon to find Plugins that do not play well with other plugins, forcing for example their own custom activity or conflicting dependencies. In that case you’ll need to do a bit of tinkering to make them work together.&lt;/p&gt;

&lt;p&gt;In case of conflicts, the Library manifest gets priority over the additional Plugins manifests. This is useful for example when using tools:replace or tools:merge to modify entries from manifests with lower priority.&lt;/p&gt;

&lt;p&gt;At build time Unity will use Gradle to generate a final manifest, merging the Library, Launcher, and all the Plugins and SDKs manifests.&lt;/p&gt;

&lt;p&gt;Some extra permissions might also be added, depending on the Editor and Project Settings. For example, if you are using the Mobile Notifications package and set a schedule, Unity will add android.permission.SCHEDULE_EXACT_ALARM and related permissions to the manifest at build time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrswmv5jvi8ujfp8yky8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrswmv5jvi8ujfp8yky8.png" alt=" " width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scripts can also cause extra permissions to be added. For example, calls to Handheld.Vibrate will add the VIBRATE permission, while most networking calls, such as UnityWebRequest, Ping, or usage of Unity Analytics, will add the INTERNET permission.&lt;/p&gt;

&lt;p&gt;You can inspect the final manifest by expanding the APK or App Bundle, then navigating to the manifest folder. &lt;/p&gt;

&lt;p&gt;Alternatively you can use the Android Studio APK Analyzer, by selecting Build - Analyze APK. Despite the name, this also works for App Bundles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvq0a48gj87ggsfbfzzoe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvq0a48gj87ggsfbfzzoe.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The folder structure will be different whether you’re building an APK or an App Bundle, and if your game uses Play Asset Delivery packs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modifying the Manifest via Scripting
&lt;/h2&gt;

&lt;p&gt;If you are using Unity 6, you can use AndroidProjectFilesModifier to modify your Manifest file after the Gradle project is created, and before Unity goes ahead and completes the build process. &lt;/p&gt;

&lt;p&gt;The Setup callback is optional and it’s called before the build starts. It’s used to define which files or external dependencies will be edited or created, but should not make any modifications or create new files directly itself. This instead should happen in OnModifyAndroidProjectFiles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class ModifyGradleProjectScript : AndroidProjectFilesModifier
{
    public override AndroidProjectFilesModifierContext Setup()
    {
        var context = new AndroidProjectFilesModifierContext();          
        context.Outputs.AddManifestFile("gamemodule/src/main/AndroidManifest.xml");
        return context;
    }

    public override void OnModifyAndroidProjectFiles(AndroidProjectFiles projectFiles)
    {
        var modifiedManifest = new AndroidManifestFile();
                    modifiedManifest.Manifest.AddUsesPermission("android.permission.RECORD_AUDIO");
        projectFiles.SetManifestFile("gamemodule/src/main/AndroidManifest.xml", modifiedManifest);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If instead you’re using Unity 2022 or older, you can use IPostGenerateGradleAndroidProject. Similarly to AndroidProjectFilesModifier, this provides a callback to modify your final Manifest file after the Gradle project is created, before Unity goes ahead and completes the build process. To do so, you can navigate to it using the provided path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostGradleBuildProcessor : IPostGenerateGradleAndroidProject
{
    public int callbackOrder { get { return 0; } }

    public void OnPostGenerateGradleAndroidProject(string path)
    {
            Debug.Log("PostGradleBuildProcessor.OnPostGenerateGradleAndroidProject - Path: " + path);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is now deprecated. &lt;/p&gt;

&lt;p&gt;Note that, since Unity uses an incremental build system, IPostGenerateGradleAndroidProject might not be invoked if no changes to the Manifest or dependencies are made since the latest build, or it could give unexpected results (such as keeping entries that should be removed). If that happens, you can clear the Build Cache and start a clean build. AndroidProjectFilesModifier, on the other hand, is safe to use with incremental builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting the merged Manifest
&lt;/h2&gt;

&lt;p&gt;While having Gradle and Unity generate the final manifest is convenient, sometimes it’s hard to understand where certain permissions or changes are coming from.&lt;/p&gt;

&lt;p&gt;In order to do so, we can use the Manifest Merger logs. &lt;/p&gt;

&lt;p&gt;After a successful build, navigate to /Library/Bee/Android/Prj/[IL2CPP or Mono2x]/Gradle/launcher/build/intermediates/manifest_merge_blame_file/[debug or release]&lt;/p&gt;

&lt;p&gt;There, you’ll find a file called manifest-merger-blame-debug-report, which is the Manifest Merger logs. This file decorates each line of the manifest with extra information about the source of that line. &lt;/p&gt;

&lt;p&gt;For example, we can see here that unityLibrary:mobilenotifications.androidlib, which is the MobileNotifications package, is adding the RECEIVE_BOOT_COMPLETED into the final manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;43  &amp;lt;uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /&amp;gt; &amp;lt;!-- Permission will be merged into the manifest of the hosting app. --&amp;gt;
43--&amp;gt;[:unityLibrary:mobilenotifications.androidlib] [path]/Library/Bee/Android/Prj/IL2CPP/Gradle/unityLibrary/mobilenotifications.androidlib/build/intermediates/merged_manifest/release/AndroidManifest.xml:8:5-81
43--&amp;gt;[:unityLibrary:mobilenotifications.androidlib] [path]/Library/Bee/Android/Prj/IL2CPP/Gradle/unityLibrary/mobilenotifications.androidlib/build/intermediates/merged_manifest/release/AndroidManifest.xml:8:22-78
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>unity3d</category>
      <category>android</category>
      <category>androiddev</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Local Testing with Android App Bundles</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Thu, 13 Feb 2025 09:32:29 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/local-testing-with-android-app-bundles-3ml0</link>
      <guid>https://dev.to/attiliohimeki/local-testing-with-android-app-bundles-3ml0</guid>
      <description>&lt;p&gt;App Bundle is an intermediate publishing format for Android, that includes all compiled code and resources, while deferring APK generation and signing to Google Play (commonly referred to as app thinning or slicing).&lt;/p&gt;

&lt;p&gt;Google Play then generates optimized APKs based on the requesting user device, so only the code and resources that are needed for that device are downloaded and installed. This reduces download and install size for the user, and avoids having to manually manage multiple APKs for different device configurations.&lt;/p&gt;

&lt;p&gt;During development, it’s convenient to be able to test your builds locally without having to upload to Google Play and creating a new release every time. To do so, we can use bundletool.&lt;/p&gt;

&lt;p&gt;If you’re on Mac, you can install it through brew:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install bundletool&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also find it on &lt;a href="https://github.com/google/bundletool/releases" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While bundletool also allows you to create App Bundles, in this guide I’ll assume you already created your AAB from Unity, Android Studio, or another authoring tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating APK set archives and installing on device
&lt;/h2&gt;

&lt;p&gt;In order to install the App bundle on device, you first need to generate a related APKs file, which stands for APK set archive. To do so, you can use the build-apks command.&lt;/p&gt;

&lt;p&gt;When running the command,  you need to sign the APKs with the right key, otherwise it will use a default debug key, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool build-apks --bundle=/build/game.aab --output=/output/game.apks&lt;br&gt;
--ks=/secrets/gamekey.keystore&lt;br&gt;
--ks-pass=file:/secrets/keystore.pwd&lt;br&gt;
--ks-key-alias=game_key_alias&lt;br&gt;
--key-pass=file:/secrets/key.pwd&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this example I’m passing the ks-pass and the key-pass using a file. If you’d rather pass the actual password in clear text, replace file: with pass:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool build-apks --bundle=/build/game.aab --output=/output/game.apks&lt;br&gt;
--ks=/secrets/gamekey.keystore&lt;br&gt;
--ks-pass=pass:mysecretpassword&lt;br&gt;
--ks-key-alias=game_key_alias&lt;br&gt;
--key-pass=pass:mysecretpassword&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you are using Play Asset Delivery, make sure to include the --local-testing flag. This will include metadata in your APKs manifest that allows the Play Feature Delivery Library to install modules and packs without connecting to Google Play.&lt;/p&gt;

&lt;p&gt;If you expand the APKs file, or load it through Android Studio, you’ll be able to inspect the individual files contained, including PAD modules and base modules:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vj8c13g8svoqfa3jt2n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vj8c13g8svoqfa3jt2n.png" alt=" " width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then install the game on a connected device by using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool install-apks --apks=/output/game.apks&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Device Specifications
&lt;/h2&gt;

&lt;p&gt;If you include --connected-device in your build-apks command, bundletool will generate an APKs that matches the specs of the connected device. Otherwise it will generate APKs for any device configurations supported by your game.&lt;/p&gt;

&lt;p&gt;Alternatively, passing --mode=universal will build a single APKs that includes all code and resources, supporting all device configurations your app supports. &lt;/p&gt;

&lt;p&gt;You can generate a spec file for your connected device by using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool get-device-spec --output=/output/device_spec.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will include a list of deviceFeatures, screen density, SDK version, device brand and model, locales, supported ABIs, glExtensions and so on.&lt;/p&gt;

&lt;p&gt;You can use this file to generate APKs that target that specific device, by using this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool build-apks --device-spec=/output/device_spec.json&lt;br&gt;
--bundle=/build/game.aab --output=/output/game.apks&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can integrate this as part of your testing deployment process to generate APKs for a list of specific devices, by storing their related device specs without needing the actual device at hand. You can also edit that file or create your own, following the existing format, to test your game in specific conditions.&lt;/p&gt;

&lt;p&gt;Given an existing APKs file, you can generate a new archive containing a subset of configurations by passing a device spec file, as shown here: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool extract-apks --apks=/output/universal_game.apks&lt;br&gt;
--output-dir=/output/targeted_.apks&lt;br&gt;
--device-spec=/output/device_spec.json&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Determining Install size
&lt;/h2&gt;

&lt;p&gt;As App bundles, and APK set archives, contain a variety of specifications for multiple devices, their install size will vary depending on the actual device it’s being installed on.&lt;/p&gt;

&lt;p&gt;You can determine the range of install sizes by using the get-size command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundletool get-size total --apks=/output/game.apks&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The output looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MIN,MAX&lt;br&gt;
2264966236,2265953472&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By default this indicates the download size of the base and Install time modules. &lt;/p&gt;

&lt;p&gt;If you want to take into account specific modules, and their dependencies, you can pass those as a comma-separated list using the -- modules flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking App Bundle signature
&lt;/h2&gt;

&lt;p&gt;Given a built App Bundle, you can check its signature using keytool:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;keytool -printcert -jarfile /build/game.aab&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will print the certificate info, including Issuer, fingerprints, validity, algorithm used, and version.&lt;/p&gt;

&lt;p&gt;If the build is unsigned, you’ll get this message:&lt;br&gt;
&lt;code&gt;Not a signed jar file&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you’re using Unity and you already supplied your keystore in the Publishing Settings, make sure you’re not making a Development Build, as they will be unsigned by default.&lt;/p&gt;

</description>
      <category>android</category>
      <category>androiddev</category>
      <category>mobile</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Unity Profiling using Superluminal</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 04 Feb 2025 20:06:12 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/unity-profiling-using-superluminal-2oc8</link>
      <guid>https://dev.to/attiliohimeki/unity-profiling-using-superluminal-2oc8</guid>
      <description>&lt;p&gt;Superluminal is a powerful sampling-based profiling tool that allows you to analyse games and applications written in C++, Rust and .NET, without making any modifications or adding performance markers.&lt;/p&gt;

&lt;p&gt;It runs on Windows, and supports Windows, Xbox One®, Xbox Series X®, PlayStation®4 and PlayStation®5 applications and games. For access to restricted platforms (consoles) you’ll need to confirm your Developer status with Superluminal, you’ll then receive the relevant plugins through an automated update.&lt;/p&gt;

&lt;p&gt;It does not support mobile platforms. If you’re working on Android, you can use simpleperf and the System Trace View from the Android Studio Profiler instead. On iOS, you can use Instruments and the Time Profiler template.&lt;/p&gt;

&lt;p&gt;In order to use it you’ll need a paid license, you can redeem a 14 days trial for evaluation purposes.&lt;/p&gt;

&lt;p&gt;Let’s see how we can leverage Superluminal to profile our game. For this guide, I’ll be using Superluminal v.1.0.6470.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symbols
&lt;/h2&gt;

&lt;p&gt;Before we start profiling and analysing our game or application, let’s make sure we have debug symbols set up correctly.&lt;/p&gt;

&lt;p&gt;Symbols are files that attach extra information to your executable, useful for debugging. They typically include function and variable names, source code filenames, line numbers, scopes, class names and so on.&lt;/p&gt;

&lt;p&gt;In the Superluminal Settings window, you can define additional symbol file locations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmozz0gvpprchx4w6ja8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftmozz0gvpprchx4w6ja8.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new PDB parser option, at the bottom of the window, provides faster symbols resolution but it could be unstable as it’s an experimental feature.&lt;/p&gt;

&lt;p&gt;Unity includes stripped debug symbols as part of the engine installation; you can find the relevant pdb files in the Editor\PlaybackEngines[Platform]\Variations folder. They also maintain a public symbol server for Windows debug symbols, providing stripped symbols, located at &lt;a href="https://symbolserver.unity3d.com/" rel="noopener noreferrer"&gt;https://symbolserver.unity3d.com/&lt;/a&gt;. There are no public symbols servers for other platforms.&lt;/p&gt;

&lt;p&gt;When making a Windows build, make sure to enable “Copy PDB files” to ensure your final executable includes debug symbols. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14g81ivptqmeiubllq3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14g81ivptqmeiubllq3f.png" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, always make sure that option is disabled before making release builds that you intend to distribute. Symbols will result in larger build size, and will provide additional information about your game source that are not meant to be shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking a capture
&lt;/h2&gt;

&lt;p&gt;Superluminal allows you to profile your game, collecting valuable runtime performance information, or the actual editor, measuring for example asset import times, build times and so on, in order to find bottlenecks and speed up your workflow.&lt;/p&gt;

&lt;p&gt;To start collecting data, you first need to copy the mono-profiler-superluminal.dll next to your executable (either the game or the editor). If you miss this step, you’ll get a prompt with a link to the DLL folder. This is normally located in the installation folder, under C:\Program Files\Superluminal\Performance\Unity. You can then pick either the x64 or x86 DLL, based on your system.&lt;/p&gt;

&lt;p&gt;Since Unity 2022.2.0f1, there is built-in support for Superluminal. If you are using an older version, you also need to add a command line argument, -monoProfiler superluminal, &lt;br&gt;
so Unity knows that it needs to look for the DLL you just provided.  &lt;/p&gt;

&lt;p&gt;When running the editor, you can add the Command line argument from the Hub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzy2rnrxg07js852ywqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzy2rnrxg07js852ywqd.png" alt=" " width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that, make sure to restart the game or the editor.&lt;/p&gt;

&lt;p&gt;If you attempt to use the argument on a version of Unity that has built-in support for Superluminal, you’ll get a warning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4npriz56h7pmuqo56x3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4npriz56h7pmuqo56x3p.png" alt=" " width="709" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the Superluminal window, you can select Run and then select the relevant executable, either for your game or for the Unity editor. This will start the application and attach the process directly.&lt;/p&gt;

&lt;p&gt;Another way to use Superluminal is to attach it to a running instance. You can filter by processes that contain “unity” in their name, and as shown, you’ll find a few of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1umy1kbqclcs5gg6jfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1umy1kbqclcs5gg6jfh.png" alt=" " width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When profiling the engine, the one you’re looking for is the Unity.exe with the window title set to your project. The others are background processes, such as AssetImporters.&lt;/p&gt;

&lt;p&gt;You can also select multiple processes if needed. If you want to include processes that may be spawned later on, you can select “Enable child process profiling” in the Capture options, and optionally define a filter to reduce noise.&lt;/p&gt;

&lt;p&gt;Sampling by default is 8khz, but can be changed from the Capture options, either when you select Run or Attach. Different platforms will offer different frequency ranges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh6nt051g6ksc7skilis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh6nt051g6ksc7skilis.png" alt=" " width="611" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown above, you can also limit the capture to a specific duration or file size.&lt;/p&gt;

&lt;p&gt;While the Unity profiler is an Instrumentation based tool, Superluminal is mainly a sampling tool. This means that you need to capture a sample that is long enough to be able to provide useful data.&lt;/p&gt;

&lt;p&gt;When you’ve collected enough data, you can stop the capture and start analysing it. At that point, Superluminal will start resolving symbols, which might take a few minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwbfsaevxctk7xwhsapc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwbfsaevxctk7xwhsapc.png" alt=" " width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that is done, a new window will appear with your capture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysing your capture
&lt;/h2&gt;

&lt;p&gt;Let’s go through the capture data now. The main window will show all available threads, and the work they’ve been doing. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabpbg2z4riz51knw1zfd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabpbg2z4riz51knw1zfd.png" alt=" " width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can expand a thread by pressing the Expand button next to it, and you can filter it or change ordering by right-clicking on it. To make things easier to navigate, you should select all the threads you don’t need and hide them.&lt;/p&gt;

&lt;p&gt;Threads are sorted by how much work they are doing, so in most cases you’ll find the Main Thread at the top. You can look for specific portions of your execution by hitting Ctrl+F, which lets you search by Function Name or Instrumentation event. You can also select a time range by dragging in the main window, then pressing F.&lt;/p&gt;

&lt;p&gt;When you hover over a specific call, you might see Dash Lines appearing. These indicate thread interactions. By clicking on it you’ll get more information on the blocking calls in the Thread Interaction tab below, including blocking and unblocking callstacks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gu12trgnbpe0cm9ntk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gu12trgnbpe0cm9ntk5.png" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Call Graph below you get a top-down view of the most expensive calls in the specified time range. This includes the function name, the module (the DLLs where it comes from), exclusive and inclusive time, and thread state (whether it’s executing or synchronising). Inclusive time refers to the time taken by the function itself and all its children recursively, while Exclusive time is time spent only in that function. &lt;/p&gt;

&lt;p&gt;You’ll also get a pie chart showing function time distribution on the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxtac4c7pk13q6a07mp9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxtac4c7pk13q6a07mp9.png" alt=" " width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Expand Hot Path button (the flame-looking one at the top of the Call Graph tab), goes through the call stack and highlights the most expensive calls, which is quite convenient.&lt;/p&gt;

&lt;p&gt;In some cases, you might want to focus on a single call and analyse from there. To do so, you can right click on a call and select “Set as Root”. This will refresh the Call graph taking only that specific call, which makes it easier to navigate.&lt;/p&gt;

&lt;p&gt;If you switch to the Function List tab, it will show a raw list of all the functions called in your sample, allowing you to sort by Exclusive time and understand what’s taking most of your CPU time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb9kskrk51tw1tsc54kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb9kskrk51tw1tsc54kl.png" alt=" " width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can right-click on a function call in the Threads view and select “Show Symbol info” to ensure relevant debug symbols can be solved in the Modules window.&lt;/p&gt;

&lt;p&gt;If you haven’t set up the symbols and added the DLL next to your executable, you’ll notice this warning in the Source and Disassembly view when selecting the function from the Functions list. You’ll then be unable to inspect source file information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5rb39pr4lpjapsg6yiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5rb39pr4lpjapsg6yiq.png" alt=" " width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Instrumentation markers
&lt;/h2&gt;

&lt;p&gt;While Superluminal is mainly a sampling tool, it also allows you to add Instrumentation markers, using the Performance API, to create event markers, and set thread names.&lt;/p&gt;

&lt;p&gt;To do that in Unity, you can use the SuperluminalPerf .NET wrapper. &lt;a href="https://www.nuget.org/packages/SuperluminalPerf/" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/SuperluminalPerf/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/xoofx/SuperluminalPerf" rel="noopener noreferrer"&gt;https://github.com/xoofx/SuperluminalPerf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First of all, initialise the API as soon as the game starts:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SuperluminalPerf.Initialize();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If Superluminal is not in the default path, you’ll need to pass the actual path for the PerformanceAPI.dll to this call.&lt;/p&gt;

&lt;p&gt;You can then create markers by starting and ending events, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SuperluminalPerf.BeginEvent("LevelLoading");&lt;br&gt;
// Your Level loading logic&lt;br&gt;
SuperluminalPerf.EndEvent();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can set the current thread name using the related call:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SuperluminalPerf.SetCurrentThreadName("LevelLoadingThread");&lt;/code&gt;&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Network Traffic Analysis with Wireshark</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Mon, 21 Oct 2024 21:35:43 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/network-traffic-analysis-with-wireshark-4cbf</link>
      <guid>https://dev.to/attiliohimeki/network-traffic-analysis-with-wireshark-4cbf</guid>
      <description>&lt;p&gt;Wireshark is a free and open-source Network Protocol Analyser, used to capture and analyse network traffic. &lt;/p&gt;

&lt;p&gt;It’s very useful to analyse how much traffic your networked game is generating during gameplay, see what endpoints are being hit and so on.&lt;/p&gt;

&lt;p&gt;It’s available on Mac, Windows and various Linux distros. You can download it from here &lt;a href="https://www.wireshark.org/download.html" rel="noopener noreferrer"&gt;https://www.wireshark.org/download.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will also need to install ChmodBPF, included in the downloaded volume, in order to capture packets. You can also optionally add tshark, capinfos, editcap, and other command line utilities to the system PATH, by running the “Add Wireshark to the system path” package.&lt;/p&gt;

&lt;h1&gt;
  
  
  Network Interface selection
&lt;/h1&gt;

&lt;p&gt;The first thing to do is select the network interface you’d like to use to analyse network traffic. &lt;br&gt;
When you open Wireshark, it will detect all available interfaces in your current device, and list them in the main screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3et45j2tsvtapw4861vp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3et45j2tsvtapw4861vp.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wireshark will show a quick preview of the traffic going through each interface while not capturing, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5k6s22b2vdph060nph6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5k6s22b2vdph060nph6r.png" alt=" " width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can optionally use the dropdown on the right to filter wired, wireless, and external interfaces (the ones you see at the bottom, requiring extra tooling). &lt;/p&gt;

&lt;p&gt;If you want to analyse traffic between applications running on the same device, you can select the loopback interface (normally called lo0 or something similar).&lt;/p&gt;

&lt;p&gt;If you are using a VPN, you will see traffic going to a virtual interface, normally named utun, tun or tap. Packets going to this interface are then sent to the VPN client for encryption, which then sends them to the VPN server.&lt;/p&gt;

&lt;p&gt;You can double click on an interface to start capturing, but before doing so, let’s make sure we have the right filters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Defining filters
&lt;/h1&gt;

&lt;p&gt;There are two types of filters, Capture and Display. As the names suggest, Capture filters are used to reduce the amount of packets captured by Wireshark. They are set before a Capture session starts, and cannot be modified afterwards. On the other hand, Display filters are used to filter the data shown in the interface, and can be modified also during and after the capture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzecy22ah2dwfow2qq7tu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzecy22ah2dwfow2qq7tu.png" alt=" " width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wireshark offers a list of predefined filters saved in the bookmarks. They are useful both to get started with some of the most common scenarios, and also to familiarise yourself with the filters syntax.&lt;/p&gt;

&lt;p&gt;For the display filters, you can check them out from Analyze - Display Filters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn51pubv0u3xwel822n3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn51pubv0u3xwel822n3g.png" alt=" " width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While for Capture filters, they are under Capture - Capture Filters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41ve0qop3ye9rjpqpjxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41ve0qop3ye9rjpqpjxu.png" alt=" " width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the syntax is slightly different between the two filter types. For example, if you wanted to analyse HTTP traffic, you would use this as capture filter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tcp port 80&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or the equivalent display filter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tcp.port == 80&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also filter by port ranges, for example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;portrange 5055-5058 or portrange 27000-27003&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The list of possible expressions is very long. You can take a look under Analyze - Display Filter Expression for an exhaustive list of display filters options, which also allows you to build filters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Taking a capture
&lt;/h1&gt;

&lt;p&gt;For the sake of this tutorial, and to give a real example, let’s assume that I’m currently working on an online multiplayer game. A client, running on my local machine, is connected to a remote dedicated server hosting a match. In addition, the client makes RESTful calls to a remote backend, and has a websocket connection to receive live updates from the back. I want to capture all traffic generated from my game during a regular session, in order to analyze it later on and share it with the rest of the team.&lt;/p&gt;

&lt;p&gt;To do so, I defined a new capture filter, called “Game Data”, with the following expression:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;udp port [GAME_SERVER_PORT] and host [GAME_SERVER_IP_ADDR]  || tcp port 443 and host [BACKEND_URL] || tcp port 443 and host [WEBSOCKET_URL]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The first bit captures all UDP traffic going through a specific GAME_SERVER_PORT port, where the host’s IP address is GAME_SERVER_IP_ADDR. The second portion captures all HTTPS traffic (both HTTPS 1.x and 2 use TCP), coming from and going to BACKEND_URL. Finally, the last portion of the filter captures the aforementioned Websocket traffic.&lt;/p&gt;

&lt;p&gt;After setting the capture filter, select Capture - Start to start capturing packets. &lt;/p&gt;

&lt;p&gt;After playing a full game session, I can then stop using Capture - Stop, and analyse the results. Since the amount of data will be significant, it’s handy to apply display filters to reduce the data shown at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tqw9gymq3dpm5lh009j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9tqw9gymq3dpm5lh009j.png" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then save the capture for later retrieval and for historical analysis, by going to File - Save as…&lt;/p&gt;

&lt;p&gt;By default, Wireshark will capture packets in Promiscuous Mode, unless the network interface or OS does not allow it, or you disable it from Preferences - Capture - “Capture packets in Promiscuous Mode”. When enabled, all packets that the network interface can see will be included in the capture, rather than just the ones meant for your machine.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conversations
&lt;/h1&gt;

&lt;p&gt;Conversations allow you to group traffic going from/to certain addresses, helping you make sense of your capture data.&lt;/p&gt;

&lt;p&gt;When opening the window, you can choose the adapter or protocol at the top, then you’ll see a list of conversations, including amount of data and packets in each direction, bitrate, the duration of the conversation and so on.&lt;/p&gt;

&lt;p&gt;As an example, here’s how it looks like when I download a file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz46t1mn3mphq8erk70u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzz46t1mn3mphq8erk70u.png" alt=" " width="800" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the Conversation Settings on the left, you can select “Name Resolution” to view resolved names, as opposed to raw addresses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cwifr6d10geb7y21oqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cwifr6d10geb7y21oqb.png" alt=" " width="385" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you then right click on a conversation, Wireshark allows you to prepare a filter that will show the conversation in the main capture window, in whichever direction you want to analyse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctz6jje7etobrbm8ycpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctz6jje7etobrbm8ycpj.png" alt=" " width="800" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After applying the filter, you can select all packets from the main window, then select File - Export Packets Dissections. This lets you export the packet list, details and plain data in various formats, such as JSON, XML, plain text or C array. You can then reconstruct the downloaded data from there. You can also try and use File - Export Objects - HTTP, then Wireshark will scan through the capture data and look for files that can be reconstructed and downloaded.&lt;/p&gt;

&lt;p&gt;An alternative way to follow conversation is by selecting a packet from the main window, then right click and selecting Follow. Depending on the packet, this will show you a list of protocol options, such as TCP Stream or TLS Stream. This will open a dialog window showing the data transferred as part of that conversation (red for client to server, blue in the opposite direction). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiou26ims6kefyhtxgnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiou26ims6kefyhtxgnl.png" alt=" " width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can optionally change the data visualisation format at the bottom. It will also apply a display filter in the main window for the relevant packets.&lt;/p&gt;

&lt;p&gt;Finally, another way to look for the start of conversations is to apply a filter for ARP (Address Resolution Protocol) packets. They are used to create the mapping of an IP address to the underlying Ethernet address, for this reason they are often found at the beginning of a conversation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Encryption
&lt;/h1&gt;

&lt;p&gt;As you might have noticed, much of your HTTP traffic will be impossible to read. That’s because most web traffic goes through TLS (Transport Layer Security). In order to make sense of it, you’ll need to decrypt it.&lt;/p&gt;

&lt;p&gt;The exact way to do that changes depending on the way the data is transferred. If you’re using curl (as most game engines do), and for most browsers, it will involve setting the SSLKEYLOGFILE environment variable, then assigning that in Preferences - Protocols - TLS, in the (Pre)-Master-Secret log filename field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfu2s0x8m6l5qn6se33u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfu2s0x8m6l5qn6se33u.png" alt=" " width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can learn more about how curl handles that here: &lt;a href="https://everything.curl.dev/usingcurl/tls/sslkeylogfile.html" rel="noopener noreferrer"&gt;https://everything.curl.dev/usingcurl/tls/sslkeylogfile.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that decrypting certain data might not be allowed. For this reason, make sure to only do this when working with your own applications or games.&lt;/p&gt;

&lt;h1&gt;
  
  
  Name Resolution
&lt;/h1&gt;

&lt;p&gt;In some cases, it can be handy to have Wireshark attempt to resolve addresses to make the capture more readable. You can do so from the Preferences - Name Resolution window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5ud9bj8jmn9mfy0t4b9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5ud9bj8jmn9mfy0t4b9.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to the default resolution methods, you can define additional DNS Servers by using the Edit button shown. Note that, when resolving IP addresses, the actual DNS queries might be added to the capture.&lt;/p&gt;

&lt;h1&gt;
  
  
  Traffic Graphs
&lt;/h1&gt;

&lt;p&gt;To make sense of your game traffic, it’s often handy to visualise it over the duration of the session, and then correlate it to what happens in-game. &lt;/p&gt;

&lt;p&gt;You can easily do so by selecting Statistics - I/O Graphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznmyth3j8a1nb0q81h13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznmyth3j8a1nb0q81h13.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom there will be a few preset filters, defined based on the protocols and data contained in your capture. You can define more filters using the Plus button, and change the Interval used to plot the graph.&lt;/p&gt;

&lt;p&gt;This is particularly useful when preparing a game review, providing a visual representation of traffic, including eventual spikes. You can export this as PDF using the Save As… button below.&lt;/p&gt;

&lt;h1&gt;
  
  
  Running from the Command Line
&lt;/h1&gt;

&lt;p&gt;While using the UI is convenient, there are cases where taking a capture via the command line is more practical, for example if you want to integrate network analysis into your automated testing pipeline, or you’re running it on a headless dedicated server.&lt;/p&gt;

&lt;p&gt;To do so, you can use tshark, which comes installed with Wireshark. For convenience, make sure to have it added to your PATH during installation.&lt;/p&gt;

&lt;p&gt;When running it without parameters, tshark will capture traffic from the first available network interface, and will print a summary of each packet to the standard output.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tshark&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can pick a network interface using the -i option, apply a capture filter using the -f option, and save the output to a capture filter with the -w option, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tshark -i en0 -f "tcp port 80"  -w ./captures/game_capture.pcap -a duration:600&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The last parameter, -a, defines the duration (in seconds) after which the capture will stop. You can also stop the capture after the size reaches a certain limit, using for example -a filesize:1000 (in KB).&lt;/p&gt;

&lt;p&gt;If you are unsure which network interfaces are available, you can have a list printed by doing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tshark -D&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can read the content of a capture file by doing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tshark -r /captures/game_capture.pcap&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These are just a few practical examples, but nearly every feature offered by Wireshark is available through tshark. You can find the full list of parameters and features here: &lt;a href="https://www.wireshark.org/docs/man-pages/tshark.html" rel="noopener noreferrer"&gt;https://www.wireshark.org/docs/man-pages/tshark.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Unity Shader Variants Optimisation and Troubleshooting</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Tue, 21 May 2024 14:52:28 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/unity-shader-variants-optimisation-and-troubleshooting-28ci</link>
      <guid>https://dev.to/attiliohimeki/unity-shader-variants-optimisation-and-troubleshooting-28ci</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This blogpost is also available on Unity's official blog: &lt;a href="https://unity.com/blog/engine-platform/shader-variants-optimization-troubleshooting-tips" rel="noopener noreferrer"&gt;https://unity.com/blog/engine-platform/shader-variants-optimization-troubleshooting-tips&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When writing shaders in Unity, we conveniently have the ability to include multiple features, passes and branching logic in a single source file. At build time, shader source files are compiled into shader programs, which contain one or more variants. A variant is a version of that shader following a single set of conditions, resulting (in most cases) in a linear execution path without static branching conditionals.&lt;/p&gt;

&lt;p&gt;The reason why we use variants, as opposed to keeping the branching paths all in one shader, is because GPUs are great at parallelising code that is predictable and always follow the same path, resulting in higher throughput. If conditionals are present in the compiled shader program, the GPU will need to spend resources doing predictive tasks, waiting for the other paths to be completed and so on, introducing inefficiencies. &lt;/p&gt;

&lt;p&gt;While this leads to significantly better GPU performance compared to dynamic branching, it also has some downsides. Build times will get longer as the number of variants increases, sometimes even by multiple hours per build. The game will also take longer to boot, as it will need to spend more time loading and prewarming shaders. Finally, you might notice significant runtime memory usage from shaders if variants are not properly managed, sometimes over 1GB. &lt;/p&gt;

&lt;p&gt;The amount of variants generated increases depending on a variety of factors, including keywords and properties defined, Quality Settings, Graphics Tiers, enabled Graphics APIs, post-processing effects, the active Rendering pipeline, Lighting and Fog modes, and whether XR is enabled, among others. Shaders that result in a large number of variants are often called Uber Shaders. Unity then loads at runtime the variant that matches the required settings and keywords, as we’ll cover later.&lt;/p&gt;

&lt;p&gt;This is particularly impactful when you consider that we often see shaders with over 100 keywords, leading to an unmanageable number of resulting variants, often referred to as Shader Variants Explosion. It’s not unusual to see shaders with an initial variant space in the millions, before any filtering is applied.&lt;/p&gt;

&lt;p&gt;To alleviate this, Unity will try and reduce the amount of variants generated based on a few filtering passes. For example, if XR is not enabled, variants that are needed for that will normally be stripped. Unity then takes into account what features you’re actually using in your scenes, such as lighting modes, fog and so on. These are particularly tricky to detect, as developers and artists could introduce seemingly safe changes, which actually leads to a significant increase in shader variants, without any obvious way to detect it unless you put some safeguards in place as part of your deployment pipeline.&lt;/p&gt;

&lt;p&gt;While this is helpful, this process is not perfect and there is a lot we can do to help Unity strip as many variants as possible without affecting the visual quality of your game.&lt;/p&gt;

&lt;p&gt;Here, I’d like to share a few practical tips on how to handle variants, how to understand where they are coming from, and some effective ways to reduce them. Your project build time and memory footprint will greatly benefit as a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Keywords impact on variants
&lt;/h2&gt;

&lt;p&gt;Shader variants are generated, among other factors, based on all possible combinations of shader_feature and multi_compile keywords used in your shader. Keywords marked as multi_compile are always included in your build, while those marked as shader_feature will be included if they are referenced by any material in your project. For this reason, you should use shader_feature whenever possible.&lt;/p&gt;

&lt;p&gt;To see what keywords are defined in a shader, you can select it and the Inspector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frocw4dl87yo1306tdzzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frocw4dl87yo1306tdzzf.png" alt=" " width="800" height="855"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, keywords are divided into Overridable and Not Overridable. Local Keywords (the ones you define in the actual shader file) with a Global scope can be overridden by a global shader keyword with a matching name. If instead they are defined at a local scope (by using multi_compile_local or shader_feature_local), they can’t be overridden and will show up in the “Not overridable” section underneath. Global shader keywords are provided by the Unity engine, and are overridable. As they can be added at any point in the build process, not all global keywords might show up in this list.&lt;/p&gt;

&lt;p&gt;Keywords can be defined in mutually exclusive groups, called sets, by defining them in the same directive. By doing this, you avoid generating variants for combinations of keywords that will never be enabled at the same time (such as two different types of lighting or fog). &lt;/p&gt;

&lt;p&gt;&lt;code&gt;#pragma shader_feature LIGHT_LOW_Q LIGHT_HIGH_Q&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To reduce the amount of keywords per platform, you can use preprocessor macros to define them only for the relevant platform, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#ifdef SHADER_API_METAL
   #pragma shader_feature IOS_FOG_FEATURE
#else
   #pragma shader_feature BASE_FOG_FEATURE
#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that these expressions with macros cannot depend on other keywords or features that are not only related to the build target.&lt;/p&gt;

&lt;p&gt;Keywords can also be limited to a specific pass, reducing the amount of potential combinations. To do so, you can add one of the following suffixes to the directive:&lt;br&gt;
_vertex&lt;br&gt;
_fragment&lt;br&gt;
_hull&lt;br&gt;
_domain&lt;br&gt;
_geometry&lt;br&gt;
_raytracing&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;code&gt;#pragma shader_feature_fragment FRAG_FEATURE_1 FRAG_FEATURE_2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This can behave differently depending on the renderer you’re using. For example, on OpenGL, OpenGL ES and Vulkan the suffixes will be ignored.&lt;/p&gt;

&lt;p&gt;You can use the directive #pragma skip_variants to define keywords that should be excluded when generating variants for that specific shader. When making your player build, all shader variants for that shader containing one of those keywords will be skipped.&lt;/p&gt;

&lt;p&gt;You can also optionally define keywords using the #pragma dynamic_branch directive, which will force Unity to rely on dynamic branching and not generate variants for those keywords. While this reduces the amount of resulting variants, it can lead to weaker GPU performance depending on the shader and game content, so it’s recommended to profile accordingly when using it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inspecting generated Shader code
&lt;/h2&gt;

&lt;p&gt;Normally, shader variants won’t be compiled until you actually build the game. Using this option, you can inspect the resulting shader variants for a specific build platform or graphics API. This allows you to check for errors ahead of time. In addition, you can paste the generated code into GPU shader performance analysis tools, such as PVRShaderEditor, for further optimisations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yrtpi1sjr4sstqs2ct4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yrtpi1sjr4sstqs2ct4.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom you will notice an entry saying how many variants are included, based on the materials present in the currently open scene, without any scriptable stripping applied. If you hit the Show button, it will show a temp file with some additional debug information on which keywords were used or stripped on various platforms, including the number of vertex stage variants.&lt;/p&gt;

&lt;p&gt;The “Preprocess only” checkbox above allows you to toggle between compiled shader code and preprocessed shader source, for easier and faster debugging.&lt;/p&gt;

&lt;p&gt;If you are using the Built-in Rendering Pipeline and working with a Surface Shader, you have the option to check the generated code that Unity will use to replace your simplified shader source when you build. You can then optionally replace your shader source with the generated code, if you’d like to modify the output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqnwfzl2prqo4d5dl66o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqnwfzl2prqo4d5dl66o.png" alt=" " width="705" height="185"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Determining what Variants are generated at build time
&lt;/h2&gt;

&lt;p&gt;When building the game, Unity will determine the variant space for each shader based on all possible permutations of its features, engine settings and other factors. These combinations are then passed to the preprocessors for multiple stripping passes. This can be extended using IPreprocessShaders callbacks, to create custom logic to strip more variants from build, as covered below.&lt;/p&gt;

&lt;p&gt;Shaders that are included as part of Always-included shaders list, under Project Settings - Graphics, will have all their variants included in the build. For this reason, it’s best to use this only when strictly necessary, as it can easily lead to a large number of variants being generated.&lt;/p&gt;

&lt;p&gt;Finally, the build pipeline will go through a process called Deduplication, identifying identical variants within the same Pass, and ensuring that they point to the same bytecode. This will result in reduced size on disk, but identical variants will still negatively affect build time, loading time and runtime memory usage, so it’s not a replacement for proper variants stripping.&lt;/p&gt;

&lt;p&gt;After a successful build, we can look into the Editor.log file to collect some useful information on what shaders variants were included in the build. To do so, search the log file for “Compiling shader” and the name of your shader. Here’s for example how it looks like on Unity 2021:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiling shader "GameShaders/MyShader" pass "Pass 1" (vp)
    Full variant space:         608
    After settings filtering:   608
    After built-in stripping:   528
    After scriptable stripping: 528
    Processed in 0.00 seconds
    starting compilation...
    finished in 0.02 seconds. Local cache hits 528 (0.16s CPU time), remote cache hits 0 (0.00s CPU time), compiled 0 variants (0.00s CPU time), skipped 0 variants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In certain cases, you might see the amount of variants increase after the settings filtering step, for example if your project has XR enabled.&lt;/p&gt;

&lt;p&gt;If your game supports multiple Graphics APIs, as mentioned in the above section, you’ll also find information for each supported renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Serialized binary data for shader GameShaders/MyShader in 0.00s
    gles3 (total internal programs: 290, unique: 193)
    vulkan (total internal programs: 290, unique: 193)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you’ll see these compression logs that will give you an indication of the final size, on disk, of the shader for a specific Graphics API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compressed shader 'GameShaders/MyShader' on vulkan from 1.35MB to 0.19MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using URP, you can select whether to have logs generated only from SRP shaders, from all Shaders, or to disable logs. To do so, select the Log Level from Project Settings - Graphics - URP Global Settings, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ujv1bzyh1pimx4gtqmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ujv1bzyh1pimx4gtqmg.png" alt=" " width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, if you select the “Export Shader Variants” option below, a JSON file will be generated after your build, containing a report of the shader variants compilations. This is available on Unity 2022.2 or newer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Determining what Variants are used at runtime
&lt;/h2&gt;

&lt;p&gt;In order to understand what shaders are actually compiled for the GPU, at runtime, you can enable the “Log Shader Compilation” option, under Project Settings - Graphics, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvvbg1rhbtvaflhngsw4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuvvbg1rhbtvaflhngsw4.png" alt=" " width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will cause your game to print in the player logs whenever a shader is compiled while you play. It will only work on Development builds and Debug mode, as described in the tooltip.&lt;/p&gt;

&lt;p&gt;The format looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiled Shader: Folder/ShaderName, pass: PASS_NAME, stage: STAGE_NAME, keywords ACTIVE_KEYWORD_1 ACTIVE_KEYWORD_2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that some platforms, such as Android, will cache compiled shaders. For this reason, you might need to uninstall and reinstall the game before doing a test pass to catch all compiled shaders.&lt;/p&gt;

&lt;p&gt;Finally, you can use the Memory Profiler package to take a snapshot of your game while it’s running, and then have an overview of what Shaders are currently loaded in memory, and their size. Sorting by size normally gives a good indication of which Shaders are bringing in the most variants, and are worth optimising.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zscc26fqnoe9077jn1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zscc26fqnoe9077jn1g.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripping based on Graphics Settings
&lt;/h2&gt;

&lt;p&gt;As part of the stripping passes, Unity will remove shader variants related to graphics features your game is not using. The process changes slightly if you are using the Built-in Rendering Pipeline, or the Universal Rendering Pipeline (URP).&lt;/p&gt;

&lt;p&gt;To define those, Go to Project Settings - Graphics. From here, while using the Built-in Rendering Pipeline, you can select what Lightmap and Fog modes your game supports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lpekzighrd4kjshs5eu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lpekzighrd4kjshs5eu.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting them to Automatic will let Unity determine what variants to strip based on the scenes included in your build. &lt;/p&gt;

&lt;p&gt;If you are unsure what features you are using, you can also use the “Import from Current Scene” button to let Unity figure out what features you need. Of course this is only helpful if all your scenes are using the same settings, so make sure to select a representative scene when using this option.&lt;/p&gt;

&lt;p&gt;If you are using the Universal Rendering Pipeline (URP), some of those options will not be used and will be hidden. Instead, you’ll be able to define what features your game requires directly in the Pipeline Settings asset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fruo9lrv5dvu1gf03vts0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fruo9lrv5dvu1gf03vts0.png" alt=" " width="800" height="1845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, unchecking “Terrain Holes” will cause all Terrain Holes Shader variants to be stripped, reducing build time as well.&lt;/p&gt;

&lt;p&gt;As you can see, URP provides more granular control on what features you want to include in your game, potentially resulting in more optimised builds with fewer unused variants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripping based on Graphics Tiers
&lt;/h2&gt;

&lt;p&gt;Note: This is only relevant when using the Built-in Rendering Pipeline. These settings will be ignored when using a scriptable rendering pipeline such as URP.&lt;/p&gt;

&lt;p&gt;Graphics Tiers are used to apply different graphics settings based on the hardware your game is running on (not to be confused with the Quality Settings). When the game starts, Unity will determine your device Graphic Tier based on hardware capabilities, Graphics API and other factors.&lt;/p&gt;

&lt;p&gt;They can be set in Project Settings - Graphics - Tier Settings, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3xplckguuxsmxxjgcxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3xplckguuxsmxxjgcxs.png" alt=" " width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on these, Unity adds these three keywords to all shaders: &lt;/p&gt;

&lt;p&gt;UNITY_HARDWARE_TIER1&lt;br&gt;
UNITY_HARDWARE_TIER2&lt;br&gt;
UNITY_HARDWARE_TIER3&lt;/p&gt;

&lt;p&gt;Then it generates shader variants for each of the Graphics tier defined. If you are not using Graphics tiers and want to avoid the related variants for them, you need to ensure all Graphics Tiers are set to exactly the same settings. Then, Unity will skip these variants.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, Unity will attempt to deduplicate variants that are identical, so if for example two of the three Tiers have the same settings, this will lead to a reduction in size on disk, even though all variants will still be generated.&lt;/p&gt;

&lt;p&gt;You can optionally force Unity to generate tier variants for a given shader and graphics renderer API, using the hardware_tier_variants as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Direct3D 11/12
#pragma hardware_tier_variants d3d11 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stripping based on Graphics APIs
&lt;/h2&gt;

&lt;p&gt;Unity compiles one set of shader variants for each graphics API included in your build, so in some cases it is beneficial to manually select the APIs and exclude the ones you don’t need.&lt;/p&gt;

&lt;p&gt;To do so, go to Project Settings - Player. By default, Auto Graphics API is selected, and Unity will include a set of built-in graphics APIs and pick one at runtime depending on the device capabilities. For example, on Android, Unity will try to use Vulkan first, and if the device does not support it, the engine falls back to GLES3.2, GLES3.1 or GLES3.0 (the variants will be identical on those GLES versions though).&lt;/p&gt;

&lt;p&gt;Instead, uncheck Auto Graphics API for the relevant platform and manually select the APIs you’d like to include. Unity will then give priority to the first one in the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkjdhxfrbnge9hl6oedx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkjdhxfrbnge9hl6oedx.png" alt=" " width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside is that you might limit the amount of devices that support your game, so make sure you know what you’re doing when changing this, and test on a variety of devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict Shader Variant Matching
&lt;/h2&gt;

&lt;p&gt;Normally, at runtime Unity tries to load the variant that is closest to the set of keywords requested, if an exact match is not available or has been stripped from the player build. While this is convenient, it also hides potential issues with your shader keywords setup. &lt;/p&gt;

&lt;p&gt;From Unity 2022.3, you can select Strict Shader Variant Matching in Project Settings - Player. This will ensure Unity only tries to load the exact match for the combination of local and global keywords you need. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftde4ozro7avj3j5jjfxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftde4ozro7avj3j5jjfxh.png" alt=" " width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If not found, it will use the Error Shader, and it will print an error in the Console, containing the shader, the subshader index, the actual pass and keywords requested. This is pretty handy when you need to track down missing variants that you actually need. As usual with stripping, this only works in the Player and has no impact in the Editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting used variants into a Shader Variants Collection
&lt;/h2&gt;

&lt;p&gt;While playing the game within the Editor, Unity keeps track of what shaders and variants are currently in use in your scene, and allows you to export that into a collection. To do that, navigate to Project Settings - Graphics. At the bottom you’ll notice a Shader Loading section, showing how many shaders are currently tracked as active. &lt;/p&gt;

&lt;p&gt;Make sure to hit Clear beforehand to have a more accurate sample, then enter Play Mode and play around with your scene, ensuring that you encounter all game elements that require specific shaders. This will increase the tracked counters. Then, press the “Save to asset…” button to save all of those in a collection asset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxocbfzgpspi4k6mkyrh7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxocbfzgpspi4k6mkyrh7.png" alt=" " width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shader Variants Collections are assets containing a list of shaders and related variants. They are commonly used to pre-define what variants you want included in your build, and to pre-warm shaders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6mwfzxvk25vtw0vnd2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj6mwfzxvk25vtw0vnd2x.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One approach used in some projects is to run this for every level of the game, saving a collection for each of them, then stripping any variants that are not present in any of those lists by using a IPreprocessShaders script (as covered in the next section). While this is convenient, in my experience it’s also fairly prone to errors. It’s hard to ensure that you encounter all required variants in a single playthrough, and some of the features might only be loaded on device and on specific cases, resulting in a list that is not necessarily accurate. As your game changes and new elements are added to the levels, or materials change, the collections will need to be updated. For this reason, I would use this mainly for debugging and investigation purposes, rather than integrating it into your build pipeline directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scriptable Shader Variants Stripping
&lt;/h2&gt;

&lt;p&gt;Whenever a shader is about to be compiled into your game build, Unity will dispatch a callback. This happens both on Player and Asset Bundles builds. We can conveniently listen to these using IPreprocessShaders.OnProcessShader and IPreprocessComputeShaders.OnProcessComputeShader (for Compute Shaders), and add custom logic to strip unnecessary variants. This way, we can greatly reduce build time, build size, and the total number of variants that get into your build.&lt;/p&gt;

&lt;p&gt;To do so, create a script that implements the IPreprocessShaders interface, then write your stripping logic within OnProcessShader. For example, here is a script that will strip all variants containing the DEBUG shader keyword, on release builds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class StripDebugVariantsPreprocessor : IPreprocessShaders
{
   public int callbackOrder =&amp;gt; 0;

   ShaderKeyword keywordToStrip;

   public StripDebugVariantsPreprocessor()
   {
      keywordToStrip = new ShaderKeyword("DEBUG");
   }


   public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList&amp;lt;ShaderCompilerData&amp;gt; data)
   {
      if (EditorUserBuildSettings.development)
      {
         return;
      }

      for (int i = data.Count - 1; i &amp;gt;= 0; i--)
      {
         if (data[i].shaderKeywordSet.IsEnabled(keywordToStrip))
         {
            data.RemoveAt(i);
         }
      }
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback order allows you to define which preprocessing script should run first, letting you create multi-steps stripping passes. Scripts with a lower priority will be executed first.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>shaders</category>
      <category>graphics</category>
      <category>optimisation</category>
    </item>
    <item>
      <title>Unity Asset Bundles, Tips and Pitfalls</title>
      <dc:creator>Attilio Carotenuto</dc:creator>
      <pubDate>Wed, 13 Mar 2024 15:10:26 +0000</pubDate>
      <link>https://dev.to/attiliohimeki/unity-asset-bundles-tips-and-pitfalls-4324</link>
      <guid>https://dev.to/attiliohimeki/unity-asset-bundles-tips-and-pitfalls-4324</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This blogpost is also available on Unity's official blog: &lt;a href="https://unity.com/blog/engine-platform/unity-asset-bundles-tips-pitfalls" rel="noopener noreferrer"&gt;https://unity.com/blog/engine-platform/unity-asset-bundles-tips-pitfalls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Asset Bundles are archive files containing assets for your game. They are used to split your game into logical blocks, allowing you to deliver and update content on demand while making your game build smaller. They’re also commonly used to deliver patches and DLCs for your game. Asset Bundles can contain all sorts of assets, such as Prefabs, Materials, Textures, Audio Clips, Scenes, and more, but they can’t include scripts.&lt;/p&gt;

&lt;p&gt;Previously, it was necessary to build Asset Bundles manually, marking each asset accordingly, then tracking and resolving dependencies by yourself at runtime. Nowadays, all of this is taken care of by the Addressables system, which will build Asset Bundles for you based on the Asset Groups you define, as well as loading and handling dependencies transparently.&lt;/p&gt;

&lt;p&gt;While there are a lot of guides on how Asset Bundles work, I’d like to cover some lesser-known aspects of the system, with a focus on game performance, memory runtime usage, and general compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading and Unloading Assets
&lt;/h2&gt;

&lt;p&gt;Whenever you attempt to use an asset contained within a bundle, Unity ensures the corresponding bundle is loaded into memory, then in turn loads the asset in memory.&lt;/p&gt;

&lt;p&gt;While it’s possible to partially load specific assets within an Asset Bundle, the opposite is not allowed. This means that as soon as an asset within an asset bundle is loaded, it can only be unloaded if the entire group of assets is no longer needed.&lt;/p&gt;

&lt;p&gt;While technically &lt;a href="https://docs.unity3d.com/ScriptReference/Resources.UnloadUnusedAssets.html" rel="noopener noreferrer"&gt;Resources.UnloadUnusedAssets&lt;/a&gt; could be used, this is discouraged especially when using Addressables. The Addressables system keeps a reference count for the bundles, and each asset loaded, and calling this method can result in objects being unloaded that the Addressables system is still tracking, potentially leading to crashes when they are then loaded again.&lt;/p&gt;

&lt;p&gt;As a result, if your bundle structure is not ideal, you will often see increasing runtime memory usage as the game goes on, leading to deteriorating performance and potential crashes. For this reason, it’s best to avoid bundles with a large amount of assets in it, as it will end up taking up a lot of runtime memory and turn into a bottleneck for your game. Instead, aim to pack assets based on how frequently they are going to be loaded and used together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engine Versions Compatibility
&lt;/h2&gt;

&lt;p&gt;Asset Bundles are generally forward compatible, so bundles built with older versions of Unity will in most cases work on games built on newer versions of Unity (assuming you do not strip the TypeTree info, as covered later). The opposite is not true, so bundles built on a version of Unity that’s newer than the one used for your game build are unlikely to load correctly.&lt;/p&gt;

&lt;p&gt;As the difference in version between the bundle and the engine used for the game build increases, compatibility becomes less likely. There are also cases where the bundle might still be loaded, but the objects contained within the bundle cannot be loaded correctly in the new version of Unity, likely due to a change in the way the objects are serialized, thus creating issues. In that case, you’ll need to rebuild your bundles to maintain compatibility.&lt;/p&gt;

&lt;p&gt;There’s also a performance cost in loading bundles from a different version of Unity, as covered in the TypeTree section below.&lt;/p&gt;

&lt;p&gt;For these reasons, it’s recommended to test thoroughly whenever you update the Unity version of your game build against existing Asset Bundles, and to also update them whenever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Platform compatibility
&lt;/h2&gt;

&lt;p&gt;Asset Bundles do not generally offer cross-platform support. While in the Editor, you will be able to load bundles from another target platform, however on-device this will fail.&lt;/p&gt;

&lt;p&gt;This is still true for bundles that contain assets that are not necessarily platform-specific.&lt;/p&gt;

&lt;p&gt;The reason for this limitation is that data might be optimized or compressed in ways that only work for the target platform. Also, bundles can contain platform-specific data that should not be shared between different platforms, so this prevents leaking content that is not intended for another platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading Cache
&lt;/h2&gt;

&lt;p&gt;The Loading cache is a shared pool of pages where Unity stores recently accessed data for your Asset Bundles. This is global, so it’s shared between all Asset Bundles within your game.&lt;/p&gt;

&lt;p&gt;This has been introduced fairly recently, I believe on Unity 2021.3, then backported to 2019.4. Before this, Unity relied on separate caches for each Asset Bundle, which resulted in significantly higher runtime memory usage (covered below in “Serialized File Buffers”).&lt;/p&gt;

&lt;p&gt;By default this is set to 1MB, but can be changed by setting AssetBundle.memoryBudgetKB. &lt;/p&gt;

&lt;p&gt;The default cache size should be enough in most cases, although there are some scenarios where changing it might bring benefits to your game. For example, if you have bundles with a lot of small objects contained within, increasing the cache size might lead to more cache hits, improving performance for your game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Internal Data
&lt;/h2&gt;

&lt;p&gt;Along with your game assets, Asset Bundles include a bunch of extra information and headers, used by Unity to know which assets to load and how, as well as a dedicated cache (depending on the Unity version you are using).&lt;/p&gt;

&lt;h4&gt;
  
  
  Table of Content
&lt;/h4&gt;

&lt;p&gt;A map of the assets in a bundle. It’s what allows you to lookup and load each individual asset in the bundle by name. Its size in memory is normally not a concern, unless you have exceptionally large asset bundles containing thousands of objects.&lt;/p&gt;

&lt;h4&gt;
  
  
  Preload Table
&lt;/h4&gt;

&lt;p&gt;The Preload Table lists the dependencies of each asset contained within your bundle. It’s used by Unity to correctly load and construct assets.&lt;/p&gt;

&lt;p&gt;This can become quite large if the assets contained in your bundle have a lot of explicit and implicit dependencies, as well as cascading dependencies coming from other bundles. For this reason (and many others), it’s a good idea to design your bundles to minimize the dependency chain.&lt;/p&gt;

&lt;h4&gt;
  
  
  TypeTrees
&lt;/h4&gt;

&lt;p&gt;TypeTrees define the serialized layout of the objects contained in the Asset Bundles.&lt;/p&gt;

&lt;p&gt;Their size depends on how many different types of objects are contained within the bundle. For this reason, it’s a good idea to avoid large bundles where objects of many different types are mixed together.&lt;/p&gt;

&lt;p&gt;TypeTrees are necessary to maintain compatibility when upgrading the Unity version of your game build while still trying to load Asset Bundles built on older versions of the engine. For example, if the format or the structure of the object have changed, they allow you to do a Safe Binary read so Unity can attempt to load it regardless. This has a performance cost, so in general it’s recommended to update bundles whenever possible when you update the engine.&lt;/p&gt;

&lt;p&gt;It can optionally be disabled, by setting the BuildAssetBundleOptions.DisableWriteTypeTree flag when building your bundles. This will make your bundles and the related memory overhead smaller, but it also means that you’ll need to rebuild all your bundles whenever you update the engine version of your game build. This is especially painful if you rely on bundles built from your players for user-generated content, so unless you have a very strong reason to do so, it’s recommended to keep TypeTrees enabled.&lt;/p&gt;

&lt;p&gt;One case where TypeTrees can normally be safely disabled is for bundles included directly in your game build. In this case, upgrading the engine would require making a new game build and new Asset Bundles anyway, so its retrocompatibility aspect isn’t relevant.&lt;/p&gt;

&lt;p&gt;Each bundle has their own TypeTrees, so having multiple small bundles containing the same type of objects will slightly increase the total size on disk. On the other hand, when loaded, TypeTrees are stored in a global cache in memory, so you won’t incur a higher runtime memory cost if multiple asset bundles are storing the same type of objects.&lt;/p&gt;

&lt;h4&gt;
  
  
  Serialized File Buffers
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since Unity 2019.4, this has been replaced by a global, shared Loading cache, as described above.&lt;/p&gt;

&lt;p&gt;When an Asset Bundle is loaded, Unity allocates internal buffers to store their serialized files into memory.&lt;/p&gt;

&lt;p&gt;Regular Asset Bundles contain one serialized file, while Streaming Scene Asset Bundles contain up to two files for each scene contained in that bundle. The size of these buffers depends on the platform. On Switch, PlayStation, and Windows RT it will be 128KB, while all other platforms have 14KB buffers.&lt;/p&gt;

&lt;p&gt;For this reason, it’s best to avoid having a large amount of very small asset bundles, since the memory occupied by these buffers might become significant compared to the assets they actually provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRC Integrity Checks
&lt;/h2&gt;

&lt;p&gt;A CRC (Cyclic Redundancy Check) is used to do checksum validation of your Asset Bundles, ensuring the content delivered to your game is exactly what you expect. CRCs are calculated based on the uncompressed content of the bundle.&lt;/p&gt;

&lt;p&gt;On consoles, Asset Bundles are normally included as part of the title installation on local storage or downloaded as DLCs, which makes CRC checks unnecessary. On other platforms, such as PC or Mobile, it’s important to do CRC checks on bundles downloaded from a CDN. This is to ensure the file is not corrupted or truncated, leading to potential crashes, and also to avoid potential tampering.&lt;/p&gt;

&lt;p&gt;CRC checks are fairly expensive in terms of CPU usage, especially on consoles and mobile. For these reasons, it’s normally a good compromise to disable CRC checks on local and on cached bundles, enabling them only on non-cached remote bundles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduced overhead on Asset Lookup
&lt;/h2&gt;

&lt;p&gt;By default, Unity offers three ways to lookup assets within bundles: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project Relative Path (Assets/Prefabs/Characters/Hero.prefab)&lt;/li&gt;
&lt;li&gt;Asset Filename (Hero)&lt;/li&gt;
&lt;li&gt;Asset Filename with Extension (Hero.prefab)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this is convenient, it comes at a cost. In order to support the last two methods, Unity needs to build lookup tables, which can consume a significant amount of memory for large bundles.&lt;/p&gt;

&lt;p&gt;In addition, loading assets using a different method than Project Relative Path will incur a performance cost, again because of the table lookup required.&lt;/p&gt;

&lt;p&gt;For these reasons, it’s recommended to avoid using those methods. You can even disable them when the Asset Bundles are built, which will improve loading performance for your asset bundles, and runtime memory usage.&lt;/p&gt;

&lt;p&gt;To do that, you can set these two flags when building your bundles:&lt;br&gt;
&lt;a href="https://docs.unity3d.com/ScriptReference/BuildAssetBundleOptions.DisableLoadAssetByFileName.html" rel="noopener noreferrer"&gt;BuildAssetBundleOptions.DisableLoadAssetByFileName&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.unity3d.com/ScriptReference/BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension.html" rel="noopener noreferrer"&gt;BuildAssetBundleOptions.DisableLoadAssetByFileNameWithExtension&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that, when using Addressables and the Scriptable Build Pipeline, this won’t be relevant as these flags are set by default.&lt;/p&gt;

&lt;h5&gt;
  
  
  References
&lt;/h5&gt;

&lt;p&gt;(1) &lt;a href="https://blog.unity.com/engine-platform/behind-the-scenes-speeding-up-unity-workflows" rel="noopener noreferrer"&gt;https://blog.unity.com/engine-platform/behind-the-scenes-speeding-up-unity-workflows&lt;/a&gt;&lt;br&gt;
(2) &lt;a href="https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/MemoryManagement.html" rel="noopener noreferrer"&gt;https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/MemoryManagement.html&lt;/a&gt;&lt;br&gt;
(3) &lt;a href="https://docs.unity3d.com/2021.3/Documentation/ScriptReference/AssetBundle-memoryBudgetKB.html" rel="noopener noreferrer"&gt;https://docs.unity3d.com/2021.3/Documentation/ScriptReference/AssetBundle-memoryBudgetKB.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>unity3d</category>
    </item>
  </channel>
</rss>
