Huge (2000+ms) lag spike when scanning image

Include the following details (edit as applicable):

  • Issue category: Image tracking
  • Device type & OS version: Android 12 on Sony Xperia 1 II and iOS on iPad Air 4th gen
  • Host machine & OS version: Mac / Windows 10
  • Issue Environment : On Device
  • Xcode version: Latest
  • ARDK version: 1.3.1
  • Unity version: 2021.3.4f1

Description of the issue:
If I scan my AR image I get a huge lag spike, while it only fires 1 event/instantiate.
This clearly shoud not happen.
Using the profiler I see this comes from the Native AR Session (see screenshot)
Is this a bug or do I need to fix anything?

Hi Dylan,

We were unable to reproduce any significant lag spike while profiling either the ImageDetection or WayspotAnchors example scenes on both an iPhone X and iPhone 13 Pro using the latest ARDK 2.0.0 release.

Additionally, iPads are not supported devices and a list of ARDK runtime requirements can be found here.

Can you confirm that the performance spike is occurring for you with an ARDK 2.0.0 example scene running on a supported device?

Hi there,
I confirmed all features and they work fine on iPad (also, why is there no official iPad support?).
The lag spike is also on Android, so it’s not an IOS issue. Can I send you our project somewhere safe

The Lightship platform has been designed for creating experiences targeting the typical mobile phone form factor. The team is interested in hearing from the developer community about their experiences with tablet deployment, although these devices are not officially supported at this time. I saw you submitted a feature request for iPad support, so thank you for that!

In regards to the performance spike problem, we’re continuing our investigation of the issue, and any additional information you can provide about your specific Unity scene is appreciated. We’re attempting to reproduce the issue with the ImageDetection example scene, so if you could reproduce the spike with that sample scene and provide screenshots, that could help us track down the root cause of the issue.

Here is the codes used:

using System;
using System.Collections.Generic;

using Niantic.ARDK.AR;
using Niantic.ARDK.AR.Configuration;
using Niantic.ARDK.AR.ReferenceImage;
using Niantic.ARDK.Extensions;
using Niantic.ARDK.Utilities.Collections;
using Niantic.ARDK.Utilities.Logging;

using UnityEngine;
using UnityEngine.Serialization;

namespace Niantic.ARDK.Extensions
{
  public class ARImageDetectionManager
    : ARConfigChanger
  {
    [Serializable]
    private struct InspectorImage
    {
      [Tooltip("The jpeg image as a bytes TextAsset. This should be a jpg file with a .bytes file extension.")]
      [SerializeField]
      public TextAsset _imageAsBytes;

      [Tooltip("A unique name for the image, which will be the IARReferenceImage's name.")]
      [SerializeField]
      public string _name;

      [Tooltip("The width of the physical image in meters.")]
      [SerializeField]
      public float _physicalWidth;
    }

    [Tooltip("Images that will be added to the set of images to be detected when this is initialized.")]
    [SerializeField]
    private InspectorImage[] _images;

    /// Images that will be used in the ARSession's configuration when it is next run, if this
    /// manager is enabled.
    public IReadOnlyCollection<IARReferenceImage> RuntimeImages
    {
      get
      {
        return _readOnlyRuntimeImages;
      }
    }

    private readonly HashSet<IARReferenceImage> _runtimeImages = new HashSet<IARReferenceImage>();
    private ARDKReadOnlyCollection<IARReferenceImage> _readOnlyRuntimeImages;

    /// Adds an image to RuntimeImages and, if this manager is enabled, request that the session be
    /// re-run.
    public void AddImage(IARReferenceImage newImage)
    {
      _runtimeImages.Add(newImage);
      if (AreFeaturesEnabled)
        RaiseConfigurationChanged();
    }

    /// Removes an image from RuntimeImages and, if this manager is enabled, request that the
    /// session be re-run.
    public void RemoveImage(IARReferenceImage badImage)
    {
      if (_runtimeImages.Remove(badImage))
      {
        if (AreFeaturesEnabled)
          RaiseConfigurationChanged();
      }
      else
      {
        ARLog._Warn("Attempting to remove an image that isn't there.");
      }
    }

    protected override void InitializeImpl()
    {
      base.InitializeImpl();

      _readOnlyRuntimeImages = _runtimeImages.AsArdkReadOnly();

      if (_images != null)
      {
        foreach (var image in _images)
        {
          AddImage
          (
            ARReferenceImageFactory.Create
            (
              image._name,
              image._imageAsBytes.bytes,
              image._imageAsBytes.bytes.Length,
              image._physicalWidth
            )
          );
        }
      }
    }

    protected override void EnableFeaturesImpl()
    {
      base.EnableFeaturesImpl();

      RaiseConfigurationChanged();
    }

    protected override void DisableFeaturesImpl()
    {
      base.DisableFeaturesImpl();

      RaiseConfigurationChanged();
    }

    public override void ApplyARConfigurationChange
    (
      ARSessionChangesCollector.ARSessionRunProperties properties
    )
    {
      if (!AreFeaturesEnabled)
        return;

      if (properties.ARConfiguration is IARWorldTrackingConfiguration worldConfig)
        worldConfig.DetectionImages = _runtimeImages;
    }
  }
}
using Niantic.ARDK.AR;
using Niantic.ARDK.AR.Anchors;
using Niantic.ARDK.AR.ARSessionEventArgs;
using Niantic.ARDK.AR.ReferenceImage;
using Niantic.ARDK.Utilities;
using Niantic.ARDK.Extensions;
using System;
using System.Collections.Generic;
using UnityEngine;

public class ImageRecognitionManager : MonoBehaviour
{

    private IARSession _session;  // A session that is initialized elsewhere.

    private Dictionary<Guid, GameObject> _detectedImages = new Dictionary<Guid, GameObject>();

    private void Start()
    {   
        ARSessionFactory.SessionInitialized += SetupSession;

        Debug.Log("started");
    }

    private void SetupSession(AnyARSessionInitializedArgs arg)
    {
        // Add listeners to all relevant ARSession events.
        _session = arg.Session;

        SetupCallbacks();

        //Debug.Log("SetupSession");
    }

    private void SetupCallbacks()
    {
        _session.SessionFailed += args => Debug.LogError(args.Error);
        _session.AnchorsAdded += OnAnchorsAdded;
        //_session.AnchorsRemoved += OnAnchorsRemoved;
    }

    private void OnAnchorsAdded(AnchorsArgs args)
    {

        foreach (var anchor in args.Anchors)
        {
            if (anchor.AnchorType != AnchorType.Image) return;

            var imageAnchor = (IARImageAnchor)anchor; //Hoeft niet?

            var imageName = imageAnchor.ReferenceImage.Name;
            //IARAnchor newAnchor = _session.AddAnchor(imageAnchor.Transform);

            GameManager.Instance.ChangeAnchor(imageAnchor);

            StateMachine.NewState(imageName);
        }
    }

    private void OnAnchorsRemoved(AnchorsArgs args)
    {
        foreach (var anchor in args.Anchors)
        {
            if (!_detectedImages.ContainsKey(anchor.Identifier))
                continue;

            Destroy(_detectedImages[anchor.Identifier]);
            _detectedImages.Remove(anchor.Identifier);
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System;
using System.Threading.Tasks;

[System.Serializable]
public struct GameState
{
    [Tooltip("Same name as name in ImageDetectionManager")] public string Name;
    [Tooltip("Time the exit animation takes to play")] public float ExitTime;
    public UnityEvent EnterEvent;
    public UnityEvent ExitEvent;
}

public class StateMachine : MonoBehaviour
{
    [SerializeField] private GameState[] _allStates;
    private static GameState _currentState;
    private static Dictionary<string, GameState> _statesDict = new Dictionary<string, GameState>();

    private void Awake()
    {
        _statesDict.Clear();
        //_statesDict.Add("start", new GameState("start"));
        foreach (var state in _allStates)
        {
            _statesDict.Add(state.Name, state);
        }

        _currentState = _statesDict["Start"];
    }

    private void Start()
    {
        //NewState("TreeV1"); //Testing
    }

    static public Dictionary<string, GameState> GetAllStates()
    {
        return _statesDict;
    }

    static async public void NewState(string state)
    {
        if (_statesDict.ContainsKey(state))
        {
            if (state != _currentState.Name) //New state
            {

                _currentState.ExitEvent.Invoke();

                await Task.Delay((int)(_currentState.ExitTime * 1000));
                _currentState = _statesDict[state];
                Debug.Log(_currentState.Name);
                _currentState.EnterEvent.Invoke();

            }
            else //Same state as current state
            {
                Debug.Log("No state with this name found");
            }
        }
        else 
        {
            Debug.LogError("No state with this name found");
        }
    }
}

Hey again!
I found the issue!
After I scanned the image I disable the gameobject with the AR Image Detection Manager and Image Recognition Manager scripts on it. I assumed this would be benefition for performance, since I only need to scan 1 image to start the experience.
It turns out disabling or destroying the gameobject containing the image detection code causes the huge lag spike!

Is this report enough or do I need to make another post?

Hi Dylan, glad you were able to identify the root cause of the performance hit in your script!

For future reference, you may want to enable or disable the ARImageDetectionManager with its inherited EnableFeatures() and DisableFeatures() methods – shown here – rather than modifying the associated GameObject’s status.

Thanks! Did not know that existed.

The lag spike still occurs when using DisableFeatures

In order to help isolate the issue, could you try running the ImageDetection sample scene located in the ARDK Example Scenes found here on your test device to see if the issue reproduces? At this time, we want to determine if this is a device-specific problem, or rather something related to your custom scene.

You may also want to update to the latest version of ARDK at this time – 2.0.0 – to see if this issue persists with the latest ARDK release.