DEV Community

GameDevToolLab
GameDevToolLab

Posted on

Unity 6.5 DirectStorage: Interpreting an 18.6% Load-Time Reduction and the 'Up to 40%' Claim

Introduction

Unity introduced DirectStorage support in Unity 6.4 and expanded it in 6.5 so qualifying custom reads through AsyncReadManager can use the Windows DirectStorage path.

This makes the feature relevant to local map chunks, master data, and other large custom binaries as well as built-in assets. It does not make network transfer, parsing, object creation, scene activation, or shaders faster. The practical question is which part of a loading pipeline is eligible, how much time that part consumes, and whether the player actually submitted work to a DirectStorage queue.

What Unity 6.4 and 6.5 Changed

Unity 6.4 divided archive work into chunks, submitted lower-level reads in parallel, and moved decompression work into jobs. On Windows, Unity can replace that queue with DirectStorage for Texture, Mesh, and DOTS/ECS Entities asset data.

Unity 6.5 extends the path to C# reads performed through AsyncReadManager. Treat the following as minimum conditions:

  • Unity 6.5 and a Windows 64-bit player
  • Enable DirectStorage turned on
  • A local file read through AsyncReadManager
  • A test separated from Editor Play Mode and ordinary .NET file I/O
  • PIX evidence that the enabled build uses a DirectStorage queue

File.ReadAllBytes, FileStream, and ordinary file access inside a third-party library do not switch automatically.

Workload Unity 6.5 status Notes
Texture and Mesh Supported Windows player with DirectStorage enabled
Local AssetBundle or Addressables Texture/Mesh Supported path Only the local-file stage is relevant
DOTS Entities asset data Supported Explicitly documented by Unity
Custom AsyncReadManager reads Conditional Verify the queue in PIX
File.ReadAllBytes / FileStream Not automatic Requires an I/O redesign
Remote Addressables download Not covered Network transfer is separate
JSON / MessagePack parsing Not covered CPU work after reading
Instantiate / scene activation Not covered Object creation and initialization
DirectStorage GPU decompression Not used Unity 6.5 does not enable it automatically

AudioClip and other undocumented asset types should be treated as unconfirmed and checked with PIX or AsyncReadManagerMetrics.

Reading the Public Benchmark Correctly

A Unity Discussions user published an HDRP test on an SSD rated at roughly 5 GB/s sequential throughput. Later loads were compared to avoid the first PSO warm-up pass.

Condition Reported load time
DirectStorage off About 7.49 seconds
DirectStorage on About 6.10 seconds
Difference 1.39 seconds

The elapsed-time reduction is:

(7.49 - 6.10) / 7.49 × 100 = 18.56%
Enter fullscreen mode Exit fullscreen mode

The load time therefore fell by approximately 18.6%.

The post described the result as about “22% faster.” That uses a rate ratio:

7.49 / 6.10 = 1.2279...
Enter fullscreen mode Exit fullscreen mode

The work completed at about 1.228 times the previous rate, a 22.8% processing-rate increase. Both numbers are valid, but “load-time reduction” should use 18.6% and state the denominator.

The same post showed HWiNFO values of about 15 MB/s off and 530 MB/s on. The poster also said the off value did not match the elapsed time and might be affected by Windows caching. That is not evidence for a “35x faster” claim.

Another user reported a 40% improvement for a Windows 10 scene containing 17 GB of Texture2D data. It is useful as a lead, not a universal prediction: detailed timings, repetitions, CPU, and SSD information were not provided.

Why “Up to 40%” Does Not Mean 40% Faster Total Loading

Unity's figure describes a favorable supported-I/O case. Total loading can also contain unsupported reads, decompression, deserialization, object creation, shader work, and scene activation.

Let p be the fraction of total time spent in eligible I/O and r the reduction in that section. The approximate total reduction is:

p × r
Enter fullscreen mode Exit fullscreen mode

If only half of a 10-second load is eligible and that half improves by 40%, the total becomes about 8 seconds: a 20% reduction.

Enable DirectStorage and Control the Build

The setting is available at:

Edit
  > Project Settings
    > Player
      > Other Settings
        > Configuration
          > Enable DirectStorage
Enter fullscreen mode Exit fullscreen mode

Unity 6.5 also exposes PlayerSettings.enableDirectStorage:

PlayerSettings.enableDirectStorage = true;
Enter fullscreen mode Exit fullscreen mode

Unity issue UUM-133978 affected prerelease 6.4/6.5 versions. Unity lists fixes in 6000.4.1f1 and 6000.5.0a9, and the 6000.5.0f1 notes describe Project Settings, version-control, and C# support. Projects upgraded from the older storage location can start with the setting disabled, so recheck it.

A custom Build Profile can override global Player settings. The following sample intentionally requires the standard Platforms > Windows profile.

Place it in Assets/Editor/DirectStorageBuildMenu.cs or an Editor-only assembly.

#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Profile;
using UnityEditor.Build.Reporting;
using UnityEngine;

public static class DirectStorageBuildMenu
{
    [MenuItem("Build/Windows/DirectStorage A-B")]
    private static void BuildBoth()
    {
        ValidateBuildContext();
        bool original = PlayerSettings.enableDirectStorage;

        try
        {
            Build(false, "Builds/DS-Off/Game.exe");
            Build(true,  "Builds/DS-On/Game.exe");
        }
        finally
        {
            PlayerSettings.enableDirectStorage = original;
        }
    }

    private static void ValidateBuildContext()
    {
        if (EditorUserBuildSettings.activeBuildTarget !=
            BuildTarget.StandaloneWindows64)
        {
            throw new InvalidOperationException(
                "Activate the Windows 64-bit target before running this menu item.");
        }

        if (BuildProfile.GetActiveBuildProfile() != null)
        {
            throw new InvalidOperationException(
                "This sample expects the standard Platforms > Windows profile.");
        }
    }

    private static void Build(bool enabled, string output)
    {
        string[] scenes = EditorBuildSettings.scenes
            .Where(scene => scene.enabled)
            .Select(scene => scene.path)
            .ToArray();

        if (scenes.Length == 0)
            throw new InvalidOperationException("No enabled scenes were found.");

        string directory = Path.GetDirectoryName(output);
        if (!string.IsNullOrEmpty(directory))
            Directory.CreateDirectory(directory);

        PlayerSettings.enableDirectStorage = enabled;
        Debug.Log(
            $"Build DS={PlayerSettings.enableDirectStorage}, Unity={Application.unityVersion}, Output={output}");

        BuildReport report = BuildPipeline.BuildPlayer(new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = output,
            target = BuildTarget.StandaloneWindows64,
            options = BuildOptions.None
        });

        if (report.summary.result != BuildResult.Succeeded)
            throw new InvalidOperationException(
                $"Build failed: {report.summary.result}");
    }
}
#endif
Enter fullscreen mode Exit fullscreen mode

The script restores the original setting and fails instead of silently switching targets. Folder names and log lines are labels, not proof. Only the enabled build should show a DirectStorage queue in PIX.

Keep the Unity patch, packages, scripting backend, Graphics API, content, and every other build option identical.

Measure a Reproducible Windows Player

Editor Play Mode includes AssetDatabase behavior and persistent Editor caches, so use Windows player builds.

First run a stress test with large supported assets or a custom binary, then measure the real startup, transition, or streaming path. If only the stress test improves, investigate parsing, object creation, activation, and main-thread work. If neither improves, investigate the setting, target types, caching, OS, and PIX queue.

Unity's AssetBundle guide states that LoadFromFile and LoadFromFileAsync open LZMA bundles through conversion to an LZ4 in-memory file. That adds whole-bundle and memory effects, so validate the DirectStorage path first with LZ4 or an uncompressed bundle. Treat a production LZMA download/cache workflow as a separate condition.

Do not mix cold and warm runs. Alternate off and on builds for at least 10 runs; 20 to 30 is better for a production decision. Keep the median and p95, and record Unity version, Git commit, Windows version, CPU, storage, drivers, BitLocker, Graphics API, scripting backend, compression, and run number.

Record AssetBundle Timing to CSV

The following stress test mounts a local AssetBundle and loads every asset. Place the Windows bundle at Assets/StreamingAssets/ds_test so it is included in the player.

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using UnityEngine;
using Debug = UnityEngine.Debug;

public sealed class BundleBenchmark : MonoBehaviour
{
    private const string CsvHeader =
        "utc,unity,condition,runIndex,mountMs,assetsMs,totalMs,assetCount";

    private string bundleName;
    private string condition;
    private int runIndex;

    private void Awake()
    {
        bundleName = Arg("-bundleName", "ds_test");
        condition = Arg("-condition", "Unknown");
        int.TryParse(Arg("-runIndex", "0"), out runIndex);
    }

    private IEnumerator Start()
    {
        yield return null;

        string path = Path.Combine(Application.streamingAssetsPath, bundleName);
        if (!File.Exists(path))
        {
            Debug.LogError($"Bundle not found: {path}");
            Quit();
            yield break;
        }

        Stopwatch total = Stopwatch.StartNew();
        Stopwatch mount = Stopwatch.StartNew();
        AssetBundleCreateRequest open = AssetBundle.LoadFromFileAsync(path);
        yield return open;
        mount.Stop();

        if (open.assetBundle == null)
        {
            Debug.LogError($"Load failed: {path}");
            Quit();
            yield break;
        }

        AssetBundle bundle = open.assetBundle;
        try
        {
            Stopwatch assets = Stopwatch.StartNew();
            AssetBundleRequest load = bundle.LoadAllAssetsAsync();
            yield return load;
            assets.Stop();
            total.Stop();

            string line = FormattableString.Invariant(
                $"{DateTime.UtcNow:O},{Application.unityVersion},{condition},{runIndex},{mount.Elapsed.TotalMilliseconds:F3},{assets.Elapsed.TotalMilliseconds:F3},{total.Elapsed.TotalMilliseconds:F3},{load.allAssets?.Length ?? 0}");

            string csv = Path.Combine(
                Application.persistentDataPath, "ds_results.csv");

            try
            {
                if (!File.Exists(csv) || new FileInfo(csv).Length == 0)
                    File.WriteAllText(csv, CsvHeader + Environment.NewLine);

                File.AppendAllText(csv, line + Environment.NewLine);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
        }
        finally
        {
            bundle.Unload(true);
            Quit();
        }
    }

    private static string Arg(string name, string fallback)
    {
        string[] args = Environment.GetCommandLineArgs();
        int index = Array.FindIndex(args, value =>
            string.Equals(value, name, StringComparison.OrdinalIgnoreCase));

        return index >= 0 && index + 1 < args.Length
            ? args[index + 1]
            : fallback;
    }

    private static void Quit()
    {
#if !UNITY_EDITOR
        Application.Quit();
#endif
    }
}
Enter fullscreen mode Exit fullscreen mode

Example launches:

Builds\DS-Off\Game.exe -bundleName ds_test -condition DS-Off -runIndex 1
Builds\DS-On\Game.exe  -bundleName ds_test -condition DS-On  -runIndex 1
Enter fullscreen mode Exit fullscreen mode

condition is only an aggregation label. In production, store OS, Graphics API, profiler state, cold/warm state, Build Profile, and diagnostic/release mode in separate columns.

Mount, Assets, and Total are wall-clock times until the coroutine resumes. They can include PlayerLoop scheduling, frame boundaries, and main-thread congestion; they are not pure storage timings. Use PIX, the Profiler, and AsyncReadManagerMetrics for queue-level analysis. The first run can also include JIT, type initialization, and first-use paths.

LoadAllAssetsAsync is deliberately a stress test. Also measure the actual Addressables key, scene, dependency, or subset-loading path used by the product.

Custom Binary Data with AsyncReadManager

AsyncReadManager.Read accepts a path and ReadCommand values containing offset, size, and destination-buffer information. A ReadHandle represents progress and completion.

This is not a mechanical replacement for File.ReadAllBytes. Production code must define unsafe-buffer ownership, allocator and release pairing, success/failure/cancellation cleanup, ReadHandle.Dispose() timing, range validation, and the boundary between background parsing and main-thread application. A mistake can cause a crash, memory corruption, or a leak, so start from Unity's official sample or a tested I/O wrapper.

Do not submit a read and immediately block the main thread on completion. Separate the pipeline and measure it as:

ReadMs  : Data reaches the buffer
ParseMs : Bytes become structured data
ApplyMs : Parsed data reaches Unity-side state
Enter fullscreen mode Exit fullscreen mode

For open-world data, read a small header and chunk table first, then prioritize nearby cells. Request granularity, priority, cancellation, and backpressure remain game-side responsibilities.

Confirm the Path with Metrics and PIX

Use AsyncReadManagerMetrics only in diagnostic builds:

using Unity.IO.LowLevel.Unsafe;

#if ENABLE_PROFILER
AsyncReadManagerMetrics.StartCollectingMetrics();
// Run the workload.
AsyncReadManagerMetrics.StopCollectingMetrics();
#endif
Enter fullscreen mode Exit fullscreen mode

-enable-file-read-metrics starts collection at process launch. Remove instrumentation and repeat the final measurement with a release-like build.

In PIX Timing Capture, enable File IO > File accesses. Compare the presence of the DirectStorage queue, Enqueue/Submit pattern, batch size, duration, I/O-waiting threads, and main-thread stalls between the two builds.

Unity supports DirectStorage on Windows 10 and 11, but Windows 11 is the better-optimized environment. First prove activation through the setting, Build Profile, and PIX queue; then compare Windows version, NVMe or SATA storage, BitLocker, and filter drivers.

On Windows 11, fsutil bypassIo can report I/O-stack blockers. It is a supporting diagnostic, not proof that Unity used DirectStorage.

fsutil bypassIo state /v "D:\Builds\DS-On"
Enter fullscreen mode Exit fullscreen mode

The command accepts a volume, directory, or file path. Combine it with PIX and elapsed-time results.

Apply Existing Loading-Optimization Experience

DirectStorage rewards rather than replaces good loading architecture:

  • Remove synchronous choke points such as large File.ReadAllBytes calls and tasks completed immediately on the main thread.
  • Chunk large files so important data can be prioritized and cancelled.
  • Overlap work: read chunk N+2, parse N+1, and apply N on the main thread.
  • Revisit bundle boundaries and duplicated dependencies.
  • Track native buffers, decompressed data, Unity objects, and GPU-upload memory as overlapping peaks.
  • Judge success by time-to-interaction, median, p95, worst-frame duration, and minimum-spec memory—not the best run on a development PC.

Production Checklist

  • [ ] Unity 6.5 Windows 64-bit player and exact patch recorded
  • [ ] Every condition except DirectStorage fixed; Git commit recorded
  • [ ] DirectStorage queue appears only in the enabled PIX capture
  • [ ] Workload uses a supported asset type or AsyncReadManager
  • [ ] Stress test and actual product path both measured
  • [ ] I/O, parsing, object creation, and activation separated
  • [ ] LZ4 or uncompressed path validated before LZMA workflow testing
  • [ ] Cold and warm runs separated; median and p95 retained
  • [ ] OS, storage, BitLocker, filters, worst frame, and memory recorded

Conclusion

Unity 6.5 extends DirectStorage beyond built-in Texture, Mesh, and DOTS data to qualifying AsyncReadManager reads.

The public 7.49-second to 6.10-second case is an 18.6% elapsed-time reduction, not a promise for every project. Unity's “up to 40%” figure applies to favorable eligible I/O; its effect on total loading depends on how much of the pipeline that I/O occupies.

Build controlled off/on Windows players, confirm the enabled queue in PIX, separate storage from parsing and application, and decide from median, p95, worst-frame time, and memory on target hardware. DirectStorage is not a substitute for loading optimization. It is a lower-level path that lets good chunking, parallelism, and measurement pay off more effectively.

References

Unity Documentation

Unity Discussions

Microsoft Documentation

Top comments (0)