void SetBottleneck(PerformanceBottleneck performanceBottleneck, SimulatorAdaptivePerformanceSubsystem subsystem)
        {
            if (subsystem == null)
            {
                return;
            }

            var targetFrameRate = Application.targetFrameRate;

            // default target framerate is -1 to use default platform framerate so we assume it's 60
            if (targetFrameRate == -1)
            {
                targetFrameRate = 60;
            }

            var currentTargetFramerateHalfMS = 1.0f / targetFrameRate / 2.0f;
            var currentTargetFramerateMS     = 1.0f / targetFrameRate;

            switch (performanceBottleneck)
            {
            case PerformanceBottleneck.CPU:     // averageOverallFrametime > targetFramerate && averageCpuFrametime >= averageOverallFrametime
                subsystem.NextCpuFrameTime     = currentTargetFramerateMS + 0.001f;
                subsystem.NextGpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextOverallFrameTime = currentTargetFramerateMS + 0.001f;
                break;

            case PerformanceBottleneck.GPU:     // averageOverallFrametime > targetFramerate && averageGpuFrametime >= averageOverallFrametime
                subsystem.NextCpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextGpuFrameTime     = currentTargetFramerateMS + 0.001f;
                subsystem.NextOverallFrameTime = currentTargetFramerateMS + 0.001f;
                break;

            case PerformanceBottleneck.TargetFrameRate:     // averageOverallFrametime == targetFramerate
                subsystem.NextCpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextGpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextOverallFrameTime = currentTargetFramerateMS;
                break;

            //PerformanceBottleneck.Unknowe - averageOverallFrametime > targetFramerate
            default:
                subsystem.NextCpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextGpuFrameTime     = currentTargetFramerateHalfMS;
                subsystem.NextOverallFrameTime = currentTargetFramerateMS + 0.001f;
                break;
            }
        }
    IEnumerator NextBottleneck(bool timeout = false)
    {
        switching = true;
        stateChangeIterations--;
        timeOuttimer = timeOut;
        Debug.Log($"State change = {stateChangeIterations}");

        switch (targetBottleneck)
        {
        case PerformanceBottleneck.CPU:
            yield return(StartCoroutine(LogResult("GPU", timeout)));

            factory.FlushObjects();
            factory.RunTest     = true;
            state               = "Ramping up GPU Load";
            factory.prefab      = gpuLoader;
            factory.spawnAmount = 0.1f;
            targetBottleneck    = PerformanceBottleneck.GPU;
            break;

        case PerformanceBottleneck.GPU:
            yield return(StartCoroutine(LogResult("TargetFrameRate", timeout)));

            factory.FlushObjects();
            factory.RunTest  = false;
            state            = "Waiting for TargetFrameRate";
            targetBottleneck = PerformanceBottleneck.TargetFrameRate;
            break;

        case PerformanceBottleneck.TargetFrameRate:
            yield return(StartCoroutine(LogResult("CPU", timeout)));

            factory.FlushObjects();
            factory.RunTest     = true;
            state               = "Ramping up CPU Load";
            factory.prefab      = cpuLoader;
            factory.spawnAmount = 1;
            targetBottleneck    = PerformanceBottleneck.CPU;
            break;
        }
        switching = false;
    }
    void StartTest(PerformanceBottleneck target)
    {
        LogResult();
        factory.FlushObjects();

        switch (target)
        {
        case PerformanceBottleneck.GPU:
            factory.RunTest     = true;
            factory.prefab      = gpuLoader;
            factory.spawnAmount = 50f;
            factory.LimitCount  = 50;
            targetBottleneck    = PerformanceBottleneck.GPU;
            testRunning         = true;
            break;

        case PerformanceBottleneck.TargetFrameRate:
            factory.RunTest  = false;
            state            = "";
            targetBottleneck = PerformanceBottleneck.TargetFrameRate;
            testRunning      = false;
            break;

        case PerformanceBottleneck.CPU:
            factory.RunTest     = true;
            factory.prefab      = cpuLoader;
            factory.spawnAmount = 1;
            factory.LimitCount  = 2000;
            targetBottleneck    = PerformanceBottleneck.CPU;
            testRunning         = true;
            break;
        }
        state = $"Changed to {target} load";
        watch.Reset();
        watch.Start();
        LogResult();
    }
    void Start()
    {
        factory      = FindObjectOfType <SampleFactory>();
        bottleneckUI = FindObjectOfType <BottleneckUI>();

        factory.RunTest = false;
        ap = Holder.Instance;

        if (ap == null || !ap.Active)
        {
            state = "Adaptive Performance not active";
            Debug.Log("[AP Boost] Adaptive Performance not active");
            return;
        }
        else
        {
            watch.Start();
            state            = "Waiting on Load";
            targetBottleneck = PerformanceBottleneck.TargetFrameRate;
            Debug.LogFormat("[AP Boost] Starting Test Timestamp : {0} s , Label : {1} , Objects : {2} \n", watch.ElapsedMilliseconds / 1000f, state, factory.internalObjs);
        }
        bottleneckStatus.text = state;
        ap.PerformanceStatus.PerformanceBoostChangeEvent += OnBoostModeEvent;
    }
        private void UpdateSubsystem()
        {
            Provider.PerformanceDataRecord updateResult = m_Subsystem.Update();

            m_PerformanceMetrics.CurrentCpuLevel = updateResult.CpuPerformanceLevel;
            m_PerformanceMetrics.CurrentGpuLevel = updateResult.GpuPerformanceLevel;
            m_ThermalMetrics.WarningLevel        = updateResult.WarningLevel;
            m_ThermalMetrics.TemperatureLevel    = updateResult.TemperatureLevel;

            if (!m_JustResumed)
            {
                // Update overall frame time
                m_OverallFrameTime.AddValue(m_Subsystem.Capabilities.HasFlag(Provider.Feature.OverallFrameTime) ? updateResult.OverallFrameTime : Time.unscaledDeltaTime);
                AddNonNegativeValue(m_GpuFrameTime, updateResult.GpuFrameTime);
                AddNonNegativeValue(m_CpuFrameTime, m_CpuFrameTimeProvider != null ? m_CpuFrameTimeProvider.CpuFrameTime : updateResult.CpuFrameTime);
                m_TemperatureTrend.Update(updateResult.TemperatureTrend, updateResult.TemperatureLevel, updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureLevel), Time.time);
            }
            else
            {
                m_TemperatureTrend.Reset(updateResult.TemperatureTrend, updateResult.TemperatureLevel, Time.time);
                m_JustResumed = false;
            }

            m_ThermalMetrics.TemperatureTrend = m_TemperatureTrend.ThermalTrend;

            // Update frame timing info and calculate performance bottleneck
            m_FrameTiming.AverageFrameTime    = m_OverallFrameTime.GetAverage();
            m_FrameTiming.CurrentFrameTime    = m_OverallFrameTime.GetMostRecentValue();
            m_FrameTiming.AverageGpuFrameTime = m_GpuFrameTime.GetAverage();
            m_FrameTiming.CurrentGpuFrameTime = m_GpuFrameTime.GetMostRecentValue();
            m_FrameTiming.AverageCpuFrameTime = m_CpuFrameTime.GetAverage();
            m_FrameTiming.CurrentCpuFrameTime = m_CpuFrameTime.GetMostRecentValue();

            float targerFrameRate = EffectiveTargetFrameRate();
            float targetFrameTime = -1.0f;

            if (targerFrameRate > 0)
            {
                targetFrameTime = 1.0f / targerFrameRate;
            }

            if (m_OverallFrameTime.GetNumValues() == m_OverallFrameTime.GetSampleWindowSize() &&
                m_GpuFrameTime.GetNumValues() == m_GpuFrameTime.GetSampleWindowSize())
            {
                PerformanceBottleneck bottleneck = BottleneckUtil.DetermineBottleneck(m_PerformanceMetrics.PerformanceBottleneck, m_FrameTiming.AverageCpuFrameTime,
                                                                                      m_FrameTiming.AverageGpuFrameTime, m_FrameTiming.AverageFrameTime, targetFrameTime);

                if (bottleneck != m_PerformanceMetrics.PerformanceBottleneck)
                {
                    m_PerformanceMetrics.PerformanceBottleneck = bottleneck;
                    var args = new PerformanceBottleneckChangeEventArgs();
                    args.PerformanceBottleneck = bottleneck;

                    if (PerformanceBottleneckChangeEvent != null)
                    {
                        PerformanceBottleneckChangeEvent.Invoke(args);
                    }
                }
            }


            if (updateResult.ChangeFlags.HasFlag(Provider.Feature.WarningLevel) ||
                updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureLevel) ||
                updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureTrend))
            {
                if (ThermalEvent != null)
                {
                    ThermalEvent.Invoke(m_ThermalMetrics);
                }
            }

            // Update PerformanceControlMode
            if (updateResult.ChangeFlags.HasFlag(Provider.Feature.PerformanceLevelControl))
            {
                if (updateResult.PerformanceLevelControlAvailable)
                {
                    if (AutomaticPerformanceControl)
                    {
                        m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.Automatic;
                    }
                    else
                    {
                        m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.Manual;
                    }
                }
                else
                {
                    m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.System;
                }
            }

            // Apply performance levels according to PerformanceControlMode
            m_AutoPerformanceLevelController.TargetFrameTime = targetFrameTime;
            m_AutoPerformanceLevelController.Enabled         = (m_DevicePerfControl.PerformanceControlMode == PerformanceControlMode.Automatic);

            PerformanceLevelChangeEventArgs levelChangeEventArgs = new PerformanceLevelChangeEventArgs();

            if (m_DevicePerfControl.PerformanceControlMode != PerformanceControlMode.System)
            {
                if (m_AutoPerformanceLevelController.Enabled)
                {
                    if (m_NewUserPerformanceLevelRequest)
                    {
                        m_AutoPerformanceLevelController.Override(m_RequestedCpuLevel, m_RequestedGpuLevel);
                        levelChangeEventArgs.ManualOverride = true;
                    }

                    m_AutoPerformanceLevelController.Update();
                }
                else
                {
                    m_DevicePerfControl.CpuLevel = m_RequestedCpuLevel;
                    m_DevicePerfControl.GpuLevel = m_RequestedGpuLevel;
                }
            }

            if (m_DevicePerfControl.Update(out levelChangeEventArgs) && PerformanceLevelChangeEvent != null)
            {
                PerformanceLevelChangeEvent.Invoke(levelChangeEventArgs);
            }

            m_PerformanceMetrics.CurrentCpuLevel = m_DevicePerfControl.CurrentCpuLevel;
            m_PerformanceMetrics.CurrentGpuLevel = m_DevicePerfControl.CurrentGpuLevel;

            m_NewUserPerformanceLevelRequest = false;
        }
        private void UpdateSubsystem()
        {
            Provider.PerformanceDataRecord updateResult = m_Subsystem.Update();

            m_ThermalMetrics.WarningLevel     = updateResult.WarningLevel;
            m_ThermalMetrics.TemperatureLevel = updateResult.TemperatureLevel;

            if (!m_JustResumed)
            {
                // Update overall frame time
                if (!m_UseProviderOverallFrameTime)
                {
                    AccumulateTimingValue(ref m_OverallFrameTimeAccu, Time.unscaledDeltaTime);
                }

                if (WillCurrentFrameRender())
                {
                    AddNonNegativeValue(m_OverallFrameTime, m_UseProviderOverallFrameTime ? updateResult.OverallFrameTime : m_OverallFrameTimeAccu);
                    AddNonNegativeValue(m_GpuFrameTime, m_GpuFrameTimeProvider == null ? updateResult.GpuFrameTime : m_GpuFrameTimeProvider.GpuFrameTime);
                    AddNonNegativeValue(m_CpuFrameTime, m_CpuFrameTimeProvider == null ? updateResult.CpuFrameTime : m_CpuFrameTimeProvider.CpuFrameTime);

                    m_OverallFrameTimeAccu = 0.0f;
                }

                m_TemperatureTrend.Update(updateResult.TemperatureTrend, updateResult.TemperatureLevel, updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureLevel), Time.time);
            }
            else
            {
                m_TemperatureTrend.Reset();
                m_JustResumed = false;
            }

            m_ThermalMetrics.TemperatureTrend = m_TemperatureTrend.ThermalTrend;

            // Update frame timing info and calculate performance bottleneck
            const float invalidTimingValue = -1.0f;

            m_FrameTiming.AverageFrameTime    = m_OverallFrameTime.GetAverageOr(invalidTimingValue);
            m_FrameTiming.CurrentFrameTime    = m_OverallFrameTime.GetMostRecentValueOr(invalidTimingValue);
            m_FrameTiming.AverageGpuFrameTime = m_GpuFrameTime.GetAverageOr(invalidTimingValue);
            m_FrameTiming.CurrentGpuFrameTime = m_GpuFrameTime.GetMostRecentValueOr(invalidTimingValue);
            m_FrameTiming.AverageCpuFrameTime = m_CpuFrameTime.GetAverageOr(invalidTimingValue);
            m_FrameTiming.CurrentCpuFrameTime = m_CpuFrameTime.GetMostRecentValueOr(invalidTimingValue);

            float targerFrameRate = EffectiveTargetFrameRate();
            float targetFrameTime = -1.0f;

            if (targerFrameRate > 0)
            {
                targetFrameTime = 1.0f / targerFrameRate;
            }

            bool triggerPerformanceBottleneckChangeEvent = false;
            bool triggerThermalEventEvent             = false;
            var  performanceBottleneckChangeEventArgs = new PerformanceBottleneckChangeEventArgs();

            if (m_OverallFrameTime.GetNumValues() == m_OverallFrameTime.GetSampleWindowSize() &&
                m_GpuFrameTime.GetNumValues() == m_GpuFrameTime.GetSampleWindowSize() &&
                m_CpuFrameTime.GetNumValues() == m_CpuFrameTime.GetSampleWindowSize())
            {
                PerformanceBottleneck bottleneck = BottleneckUtil.DetermineBottleneck(m_PerformanceMetrics.PerformanceBottleneck, m_FrameTiming.AverageCpuFrameTime,
                                                                                      m_FrameTiming.AverageGpuFrameTime, m_FrameTiming.AverageFrameTime, targetFrameTime);

                if (bottleneck != m_PerformanceMetrics.PerformanceBottleneck)
                {
                    m_PerformanceMetrics.PerformanceBottleneck = bottleneck;
                    performanceBottleneckChangeEventArgs.PerformanceBottleneck = bottleneck;
                    triggerPerformanceBottleneckChangeEvent = (PerformanceBottleneckChangeEvent != null);
                }
            }

            triggerThermalEventEvent = (ThermalEvent != null) &&
                                       (updateResult.ChangeFlags.HasFlag(Provider.Feature.WarningLevel) ||
                                        updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureLevel) ||
                                        updateResult.ChangeFlags.HasFlag(Provider.Feature.TemperatureTrend));

            // The Subsystem may have changed the current levels (e.g. "timeout" of Samsung subsystem)
            if (updateResult.ChangeFlags.HasFlag(Provider.Feature.CpuPerformanceLevel))
            {
                m_DevicePerfControl.CurrentCpuLevel = updateResult.CpuPerformanceLevel;
            }
            if (updateResult.ChangeFlags.HasFlag(Provider.Feature.GpuPerformanceLevel))
            {
                m_DevicePerfControl.CurrentGpuLevel = updateResult.GpuPerformanceLevel;
            }

            // Update PerformanceControlMode
            if (updateResult.ChangeFlags.HasFlag(Provider.Feature.PerformanceLevelControl))
            {
                if (updateResult.PerformanceLevelControlAvailable)
                {
                    if (AutomaticPerformanceControl)
                    {
                        m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.Automatic;
                    }
                    else
                    {
                        m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.Manual;
                    }
                }
                else
                {
                    m_DevicePerfControl.PerformanceControlMode = PerformanceControlMode.System;
                }
            }

            // Apply performance levels according to PerformanceControlMode
            m_AutoPerformanceLevelController.TargetFrameTime = targetFrameTime;
            m_AutoPerformanceLevelController.Enabled         = (m_DevicePerfControl.PerformanceControlMode == PerformanceControlMode.Automatic);

            PerformanceLevelChangeEventArgs levelChangeEventArgs = new PerformanceLevelChangeEventArgs();

            if (m_DevicePerfControl.PerformanceControlMode != PerformanceControlMode.System)
            {
                if (m_AutoPerformanceLevelController.Enabled)
                {
                    if (m_NewUserPerformanceLevelRequest)
                    {
                        m_AutoPerformanceLevelController.Override(m_RequestedCpuLevel, m_RequestedGpuLevel);
                        levelChangeEventArgs.ManualOverride = true;
                    }

                    m_AutoPerformanceLevelController.Update();
                }
                else
                {
                    m_DevicePerfControl.CpuLevel = m_RequestedCpuLevel;
                    m_DevicePerfControl.GpuLevel = m_RequestedGpuLevel;
                }
            }

            if (m_DevicePerfControl.Update(out levelChangeEventArgs) && PerformanceLevelChangeEvent != null)
            {
                PerformanceLevelChangeEvent.Invoke(levelChangeEventArgs);
            }

            m_PerformanceMetrics.CurrentCpuLevel = m_DevicePerfControl.CurrentCpuLevel;
            m_PerformanceMetrics.CurrentGpuLevel = m_DevicePerfControl.CurrentGpuLevel;

            m_NewUserPerformanceLevelRequest = false;

            // PerformanceLevelChangeEvent triggers before those since it's useful for the user to know when the auto cpu/gpu level controller already made adjustments
            if (triggerThermalEventEvent)
            {
                ThermalEvent.Invoke(m_ThermalMetrics);
            }
            if (triggerPerformanceBottleneckChangeEvent)
            {
                PerformanceBottleneckChangeEvent(performanceBottleneckChangeEventArgs);
            }
        }
        public static PerformanceBottleneck DetermineBottleneck(PerformanceBottleneck prevBottleneck, float averageCpuFrameTime, float averageGpuFrametime, float averageOverallFrametime, float targetFrameTime)
        {
            if (HittingFrameRateLimit(averageOverallFrametime, prevBottleneck == PerformanceBottleneck.TargetFrameRate ? 0.03f : 0.02f, targetFrameTime))
            {
                return(PerformanceBottleneck.TargetFrameRate);
            }

            if (averageGpuFrametime >= averageOverallFrametime)
            {
                // GPU is active all the time? It's probably the bottleneck
                return(PerformanceBottleneck.GPU);
            }
            else if (averageCpuFrameTime >= averageOverallFrametime)
            {
                return(PerformanceBottleneck.CPU);
            }
            else
            {
                bool wasGpuBound = prevBottleneck == PerformanceBottleneck.GPU;
                bool wasCpuBound = prevBottleneck == PerformanceBottleneck.CPU;

                float gpuUtilization = averageGpuFrametime / averageOverallFrametime;
                float cpuUtilization = averageCpuFrameTime / averageOverallFrametime;

                // very high main thread CPU time => most likely CPU bound
                float highCpuUtilThreshold = wasCpuBound ? 0.87f : 0.90f;
                if (cpuUtilization > highCpuUtilThreshold)
                {
                    return(PerformanceBottleneck.CPU);
                }

                // GPU is active almost all the time? It's probably the bottleneck
                float highGpuUtilThreshold = wasGpuBound ? 0.87f : 0.90f;
                if (averageGpuFrametime > highGpuUtilThreshold)
                {
                    return(PerformanceBottleneck.GPU);
                }

                if (averageGpuFrametime > averageCpuFrameTime)
                {
                    // higher GPU time compared to CPU time? => might be GPU bound
                    // but we can only be somewhat sure if we have relatively high GPU utilization

                    float gpuUtilizationThreshold = wasGpuBound ? 0.7f : 0.72f;
                    if (gpuUtilization > gpuUtilizationThreshold)
                    {
                        // significantly higher GPU time compared to CPU time?
                        float gpuFactor = wasGpuBound ? 0.72f : 0.70f;
                        if (averageGpuFrametime * gpuFactor > averageCpuFrameTime)
                        {
                            return(PerformanceBottleneck.GPU);
                        }
                    }
                }
                else
                {
                    float cpuUtilizationThreshold = wasCpuBound ? 0.5f : 0.52f;
                    if (cpuUtilization > cpuUtilizationThreshold && averageGpuFrametime < averageCpuFrameTime)
                    {
                        // higher CPU time compared to GPU time?
                        float cpuFactor = wasCpuBound ? 0.85f : 0.80f;
                        if (averageCpuFrameTime * cpuFactor > averageGpuFrametime)
                        {
                            return(PerformanceBottleneck.CPU);
                        }
                    }
                }
            }

            return(PerformanceBottleneck.Unknown);
        }