#include <Tests/Game/SampleGameFixture.h>
#include <AzCore/Debug/Timer.h>
#include <AzCore/Math/random.h>
#include <AzCore/Asset/AssetManager.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/AttachmentSkin.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/MultiThreadScheduler.h>
#include <EMotionFX/Source/MotionSet.h>
#include <EMotionFX/Source/Importer/Importer.h>
#include <EMotionFX/Source/SingleThreadScheduler.h>
#include <EMotionFX/Source/Parameter/BoolParameter.h>
#include <EMotionFX/Source/Parameter/IntParameter.h>
#include <EMotionFX/Source/Parameter/FloatParameter.h>
#include <EMotionFX/Source/Parameter/Vector2Parameter.h>
#include <EMotionFX/Source/Parameter/Vector3Parameter.h>
#include <AzFramework/Physics/Utils.h>

namespace EMotionFX
{
    struct SampleParameterSet
    {
        std::vector<std::tuple<std::string, AZ::Vector2>> m_vec2Params;
        std::vector<std::pair<std::string, bool>> m_boolParams;
        std::vector<std::pair<std::string, float>> m_floatParams;
    };

    struct PerformanceTestParameters
    {
        const char* m_description;
        float m_fps;
        float m_motionSamplingRate;
        float m_totalTestTimeInSeconds;
        size_t m_numInstances;
        size_t m_numSkinAttachmentsPerInstance;
        AZ::u32 m_lodLevel;
        bool m_includeSoftwareSkinning;
        bool m_useMultiThreading;

        void Print() const
        {
            AZ_Printf("EMotionFX", "-------------------------------\n");
            AZ_Printf("EMotionFX", "- Performance Test Parameters -\n");
            AZ_Printf("EMotionFX", "- Description: %s -\n", m_description);
            AZ_Printf("EMotionFX", "- FPS:            %f\n", m_fps);
            AZ_Printf("EMotionFX", "- Motion Sampling Rate:  %f\n", m_motionSamplingRate);
            AZ_Printf("EMotionFX", "- Total Time (s): %f\n", m_totalTestTimeInSeconds);
            const size_t numFrames = static_cast<int>(m_totalTestTimeInSeconds * m_fps);
            AZ_Printf("EMotionFX", "- Frames:         %d\n", numFrames);
            AZ_Printf("EMotionFX", "- Frames:         %d\n", numFrames);
            AZ_Printf("EMotionFX", "- Num Instances:  %d\n", m_numInstances);
            AZ_Printf("EMotionFX", "- Num Skin Attachents per Instance: %d\n", m_numSkinAttachmentsPerInstance);
            AZ_Printf("EMotionFX", "- LOD Level:      %d\n", m_lodLevel);
            AZ_Printf("EMotionFX", "- Software Skin:  %s\n", m_includeSoftwareSkinning ? "True" : "False");
            AZ_Printf("EMotionFX", "- Multi-threading:%s\n", m_useMultiThreading ? "True" : "False");
        }
    };

    class PerformanceTestFixture
        : public SampleGameFixture
        , public ::testing::WithParamInterface<PerformanceTestParameters>
    {
    public:
        void SetUp() override
        {
            SampleGameFixture::SetUp();

            AZ::SerializeContext* serializeContext = nullptr;
            AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
            if (serializeContext)
            {
                Physics::ReflectionUtils::ReflectPhysicsApi(serializeContext);
            }

            m_random = new AZ::SimpleLcgRandom();
            m_random->SetSeed(875960);

            SampleParameterSet params;
            params.m_floatParams.push_back({ "movement_speed", 0.0f });
            params.m_vec2Params.push_back({"movement_direction", AZ::Vector2(0.0f, 0.0f) });
            params.m_boolParams.push_back({"jumping", false});
            params.m_boolParams.push_back({"attacking", true});
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 1.0f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(1.0f, 0.0f) });
            params.m_boolParams.push_back({ "jumping", false });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 0.5f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(0.5f, 0.5f) });
            params.m_boolParams.push_back({ "jumping", false });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 0.0f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(0.0f, 0.0f) });
            params.m_boolParams.push_back({ "jumping", true });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);
        }

        void TearDown() override
        {
            delete m_random;
            SampleGameFixture::TearDown();
        }

        float RandomRange(float min, float max) const
        {
            return min + m_random->GetRandomFloat() * (max - min);
        }

        AZ::Vector3 RandomRangeVec3(float min, float max) const
        {
            return AZ::Vector3(
                RandomRange(min, max),
                RandomRange(min, max),
                RandomRange(min, max));
        }

        void RandomizeParameters(const AZStd::vector<ActorInstance*>& actorInstances)
        {
            for (ActorInstance* actorInstance : actorInstances)
            {
                AnimGraphInstance* animGraphInstance = actorInstance->GetAnimGraphInstance();
                AZ_Assert(animGraphInstance, "Actor instance should have an anim graph instance playing.");
                const AnimGraph* animGraph = animGraphInstance->GetAnimGraph();

                const size_t parameterSetIndex = static_cast<size_t>(RandomRange(0.0f, static_cast<float>(m_pyroParameterSet.size())-0.0001f));
                const SampleParameterSet& params = m_pyroParameterSet[parameterSetIndex];

                const size_t numParameters = animGraph->GetNumValueParameters();
                for (size_t i = 0; i < numParameters; ++i)
                {
                    const ValueParameter* parameter = animGraph->FindValueParameter(i);
                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(i));

                    if (parameter->RTTI_GetType() == azrtti_typeid<BoolParameter>())
                    {
                        const BoolParameter* boolParameter = static_cast<const BoolParameter*>(parameter);
                        MCore::AttributeBool* boolAttribute = static_cast<MCore::AttributeBool*>(attribute);
                        boolAttribute->SetValue(boolParameter->GetDefaultValue());
                    }
                    if (parameter->RTTI_GetType() == azrtti_typeid<FloatParameter>())
                    {
                        const FloatParameter* floatParameter = static_cast<const FloatParameter*>(parameter);
                        MCore::AttributeFloat* floatAttribute = static_cast<MCore::AttributeFloat*>(attribute);
                        floatAttribute->SetValue(floatParameter->GetDefaultValue());
                    }
                    if (parameter->RTTI_GetType() == azrtti_typeid<Vector2Parameter>())
                    {
                        const Vector2Parameter* vec2Parameter = static_cast<const Vector2Parameter*>(parameter);
                        MCore::AttributeVector2* vec2Attribute = static_cast<MCore::AttributeVector2*>(attribute);
                        vec2Attribute->SetValue(vec2Parameter->GetDefaultValue());
                    }
                }

                for (const auto& boolParamPair : params.m_boolParams)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(boolParamPair.first.c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), boolParamPair.first.c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const BoolParameter* boolParameter = static_cast<const BoolParameter*>(parameter);
                    MCore::AttributeBool* boolAttribute = static_cast<MCore::AttributeBool*>(attribute);
                    boolAttribute->SetValue(boolParamPair.second);
                }

                for (const auto& vec2ParamTuple : params.m_vec2Params)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(std::get<0>(vec2ParamTuple).c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), std::get<0>(vec2ParamTuple).c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const Vector2Parameter* vec2Parameter = static_cast<const Vector2Parameter*>(parameter);
                    MCore::AttributeVector2* vec2Attribute = static_cast<MCore::AttributeVector2*>(attribute);
                    vec2Attribute->SetValue(std::get<1>(vec2ParamTuple));
                }

                for (const auto& floatParamPair : params.m_floatParams)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(floatParamPair.first.c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), floatParamPair.first.c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const FloatParameter* floatParameter = static_cast<const FloatParameter*>(parameter);
                    MCore::AttributeFloat* floatAttribute = static_cast<MCore::AttributeFloat*>(attribute);
                    floatAttribute->SetValue(floatParamPair.second);
                }
            }
        }

        static AZStd::tuple<float, float, float, float> CalculateStats(const AZStd::vector<float>& samples)
        {
            const size_t numSamples = samples.size();
            if (numSamples == 0)
            {
                return { 0.0f, 0.0f, 0.0f, 0.0f };
            }

            float best = samples[0];
            float worst = samples[0];
            float accumulated = 0.0f;
            for (const float sample : samples)
            {
                best = AZ::GetMin<float>(best, sample);
                worst = AZ::GetMax<float>(worst, sample);
                accumulated += sample;
            }
            const float mean = accumulated / static_cast<float>(numSamples);

            float variance = 0.0f;
            for (const float sample : samples)
            {
                variance += powf(sample - mean, 2.0f);
            }
            variance = variance / static_cast<float>(numSamples);
            const float stdDeviation = sqrtf(variance);

            return { best, mean, worst, stdDeviation };
        }

        void PrintReport(const AZStd::vector<float>& transformUpdateFrameTimes,
            const AZStd::vector<float>& meshDeformFrameTimes,
            float totalTransformUpdateTime,
            float totalMeshDeformTime)
        {
            const PerformanceTestParameters& param = GetParam();

            AZ_Printf("EMotionFX", "----------------------------------------------------\n");
            AZ_Printf("EMotionFX", "- Performance Test Report                          -\n");
            if (param.m_includeSoftwareSkinning)
            {
                AZ_Printf("EMotionFX", "- Totals:\n");
                AZ_Printf("EMotionFX", "    Total Time (s):                  %.4f s\n", totalTransformUpdateTime + totalMeshDeformTime);
                AZ_Printf("EMotionFX", "    Total Transform Update Time (s): %.4f s\n", totalTransformUpdateTime);
                AZ_Printf("EMotionFX", "    Total Mesh Deform Time (s):      %.4f s\n", totalMeshDeformTime);
                AZ_Printf("EMotionFX", "    Transform Mesh Ratio:            %.4f %%\n", totalTransformUpdateTime / totalMeshDeformTime);
            }
            else
            {
                AZ_Printf("EMotionFX", "- Total Time (s):                    %.4f s\n", totalTransformUpdateTime + totalMeshDeformTime);
            }

            float transformBest;
            float transformMean;
            float transformWorst;
            float transformStdDeviation;
            AZStd::tie(transformBest, transformMean, transformWorst, transformStdDeviation) = CalculateStats(transformUpdateFrameTimes);

            AZ_Printf("EMotionFX", "- Transform update:\n");
            AZ_Printf("EMotionFX", "    Best Frame:                      %.4f ms (%.1f FPS)\n", transformBest * 1000.0f, transformBest > 0.0f ? 1.0f / transformBest : 0.0f);
            AZ_Printf("EMotionFX", "    Mean Frame:                      %.4f ms (%.1f FPS)\n", transformMean * 1000.0f, transformMean > 0.0f ? 1.0f / transformMean : 0.0f);
            AZ_Printf("EMotionFX", "    Worst Frame:                     %.4f ms (%.1f FPS)\n", transformWorst * 1000.0f, transformWorst > 0.0f ? 1.0f / transformWorst : 0.0f);
            AZ_Printf("EMotionFX", "    Std Deviation:                   %.4f ms\n", transformStdDeviation * 1000.0f);

            if (param.m_includeSoftwareSkinning)
            {
                float meshDeformBest;
                float meshDeformMean;
                float meshDeformWorst;
                float meshDeformStdDeviation;
                AZStd::tie(meshDeformBest, meshDeformMean, meshDeformWorst, meshDeformStdDeviation) = CalculateStats(meshDeformFrameTimes);

                AZ_Printf("EMotionFX", "- Mesh deforms:\n");
                AZ_Printf("EMotionFX", "    Best Frame:                      %.4f ms\n", meshDeformBest * 1000.0f);
                AZ_Printf("EMotionFX", "    Mean Frame:                      %.4f ms\n", meshDeformMean * 1000.0f);
                AZ_Printf("EMotionFX", "    Worst Frame:                     %.4f ms\n", meshDeformWorst * 1000.0f);
                AZ_Printf("EMotionFX", "    Std Deviation:                   %.4f ms\n", meshDeformStdDeviation * 1000.0f);
            }

            AZ_Printf("EMotionFX", "----------------------------------------------------\n");
        }

    private:
        AZ::SimpleLcgRandom* m_random;
        std::vector<SampleParameterSet> m_pyroParameterSet;
    };

    TEST_P(PerformanceTestFixture, PerformanceTest)
    {
        const PerformanceTestParameters& param = GetParam();
        const size_t numIterations = static_cast<size_t>(param.m_totalTestTimeInSeconds * param.m_fps);
        const float frameTimeDelta = 1.0f / param.m_fps;
        param.Print();

        ActorManager* actorManager = GetEMotionFX().GetActorManager();
        ActorUpdateScheduler* scheduler = nullptr;
        if (param.m_useMultiThreading)
        {
            scheduler = MultiThreadScheduler::Create();
        }
        else
        {
            scheduler = SingleThreadScheduler::Create();
        }
        actorManager->SetScheduler(scheduler);

        const AZStd::string assetFolder = GetAssetFolder();
        GetEMotionFX().SetMediaRootFolder(assetFolder.c_str());
        GetEMotionFX().InitAssetFolderPaths();

        const char* actorFilename     = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\Actor\\rinActor.actor";
        const char* motionSetFilename = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\AnimationEditorFiles\\Advanced_RinLocomotion.motionset";
        const char* animGraphFilename = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\AnimationEditorFiles\\Advanced_RinLocomotion.animgraph";

        Importer* importer = GetEMotionFX().GetImporter();
        importer->SetLoggingEnabled(false);

        Actor* actor = importer->LoadActor(ResolvePath(actorFilename));
        ASSERT_NE(actor, nullptr) << "Actor failed to load.";
        MotionSet* motionSet = importer->LoadMotionSet(ResolvePath(motionSetFilename));
        ASSERT_NE(motionSet, nullptr) << "Motion set failed to load.";

        AnimGraph* animGraph = importer->LoadAnimGraph(ResolvePath(animGraphFilename));
        ASSERT_NE(animGraph, nullptr) << "Anim graph failed to load.";

        AZStd::vector<ActorInstance*> actorInstances;
        AZStd::vector<ActorInstance*> actorInstancesIncludingAttachments;
        actorInstances.reserve(param.m_numInstances);
        for (size_t i = 0; i < param.m_numInstances; ++i)
        {
            ActorInstance* actorInstance = ActorInstance::Create(actor);
            if (!AZ::IsClose(param.m_motionSamplingRate, 0.0f, AZ::g_fltEps))
            {
                actorInstance->SetMotionSamplingRate(1.0f / param.m_motionSamplingRate);
            }
            actorInstance->SetLocalSpacePosition(RandomRangeVec3(-100.0f, 100.0f));
            actorInstances.emplace_back(actorInstance);
            actorInstancesIncludingAttachments.emplace_back(actorInstance);

            AnimGraphInstance* animGraphInstance = AnimGraphInstance::Create(animGraph, actorInstance, motionSet);
            actorInstance->SetAnimGraphInstance(animGraphInstance);

            actorInstance->SetLODLevel(param.m_lodLevel);
            actorInstance->UpdateTransformations(0.0f);

            for (size_t attachmentNr = 0; attachmentNr < param.m_numSkinAttachmentsPerInstance; ++attachmentNr)
            {
                ActorInstance* attachmentActorInstance = ActorInstance::Create(actor);
                EMotionFX::Attachment* attachment = EMotionFX::AttachmentSkin::Create(actorInstance, attachmentActorInstance);
                actorInstance->AddAttachment(attachment);
                actorInstancesIncludingAttachments.emplace_back(attachmentActorInstance);
            }
        }

        motionSet->Preload();
        const auto& motionEntries = motionSet->GetMotionEntries();
        for (const auto& motionEntryPair : motionEntries)
        {
            const MotionSet::MotionEntry* motionEntry = motionEntryPair.second;
            ASSERT_NE(motionEntry->GetMotion(), nullptr);
        }

        AZ::Debug::Timer timer;
        AZStd::vector<float> transformUpdateFrameTimes;
        AZStd::vector<float> meshDeformFrameTimes;
        transformUpdateFrameTimes.reserve(numIterations);
        meshDeformFrameTimes.reserve(numIterations);
        float totalTransformUpdateTime = 0.0f;
        float totalMeshDeformTime = 0.0f;

        const float randomizeParametersEvery = 1.0; 
        float randomizeParameterTimer = 0.0f;

        for (size_t i = 0; i < numIterations; ++i)
        {
            randomizeParameterTimer += frameTimeDelta;
            if (randomizeParameterTimer >= randomizeParametersEvery)
            {
                RandomizeParameters(actorInstances);
                randomizeParameterTimer = 0.0;
            }

            timer.Stamp();
            GetEMotionFX().Update(frameTimeDelta);
            const float transformUpdateTime = timer.GetDeltaTimeInSeconds();
            totalTransformUpdateTime += transformUpdateTime;
            transformUpdateFrameTimes.emplace_back(transformUpdateTime);

            if (param.m_includeSoftwareSkinning)
            {
                timer.Stamp();
                for (ActorInstance* actorInstance : actorInstancesIncludingAttachments)
                {
                    actorInstance->UpdateMeshDeformers(frameTimeDelta);
                }
                const float meshDeformTime = timer.GetDeltaTimeInSeconds();
                totalMeshDeformTime += meshDeformTime;
                meshDeformFrameTimes.emplace_back(meshDeformTime);
            }
        }

        PrintReport(transformUpdateFrameTimes, meshDeformFrameTimes, totalTransformUpdateTime, totalMeshDeformTime);

        for (ActorInstance* actorInstance : actorInstancesIncludingAttachments)
        {
            actorInstance->Destroy();
        }
        actor->Destroy();
        delete animGraph;
        delete motionSet;
    }

    const float g_updatesPerSec = 60.0f;
    const float g_totalTestTime = 60.0f; 
    const size_t g_numInstances = 100;

    std::vector<PerformanceTestParameters> performanceTestData
    {

        {
            "Baseline",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            false 
        },

        {
            "Multi-threading",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            true 
        },

        {
            "Multi-threading with 1 skin attachments",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            1, 
            0, 
            false, 
            true 
        },

        {
            "Multi-threading and restricted motion sampling rate",
            g_updatesPerSec, 
            60.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            true 
        },

        {
            "Multi-threading and lower motion sampling rate",
            g_updatesPerSec, 
            30.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            true 
        },

        {
            "Multi-threading and lower motion sampling rate",
            g_updatesPerSec, 
            10.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            true 
        },

        {
            "Multi-threading at LOD level = 1",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            1, 
            false, 
            true 
        },

        {
            "Multi-threading at LOD level = 2",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            2, 
            false, 
            true 
        },

        {
            "Multi-threading at LOD level = 3",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            3, 
            false, 
            true 
        },

        {
            "Multi-threading at LOD level = 4",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            4, 
            false, 
            true 
        },

        {
            "Server test: LOD0",
            30.0f, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            false 
        },

        {
            "Server test: LOD4",
            30.0f, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            4, 
            false, 
            false 
        },

        {
            "Server test: Server actor optimization (bone removal)",
            30.0f, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            false, 
            false 
        },

        {
            "Software skinning",
            g_updatesPerSec, 
            0.0f, 
            g_totalTestTime, 
            g_numInstances, 
            0, 
            0, 
            true, 
            false 
        },
    };

    std::vector<PerformanceTestParameters> debugTestData
    {
        {
            "debug",
            g_updatesPerSec, 
            60.0f, 
            g_totalTestTime, 
            1, 
            0, 
            0, 
            false, 
            true 
        }
    };

    INSTANTIATE_TEST_CASE_P(DISABLED_PerformanceTests,
        PerformanceTestFixture,
        ::testing::ValuesIn(performanceTestData));
}

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#include <Tests/Game/SampleGameFixture.h>
#include <AzCore/Debug/Timer.h>
#include <AzCore/Math/random.h>
#include <AzCore/Asset/AssetManager.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/AttachmentSkin.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/MultiThreadScheduler.h>
#include <EMotionFX/Source/MotionSet.h>
#include <EMotionFX/Source/Importer/Importer.h>
#include <EMotionFX/Source/SingleThreadScheduler.h>
#include <EMotionFX/Source/Parameter/BoolParameter.h>
#include <EMotionFX/Source/Parameter/IntParameter.h>
#include <EMotionFX/Source/Parameter/FloatParameter.h>
#include <EMotionFX/Source/Parameter/Vector2Parameter.h>
#include <EMotionFX/Source/Parameter/Vector3Parameter.h>
#include <AzFramework/Physics/Utils.h>


namespace EMotionFX
{
    struct SampleParameterSet
    {
        std::vector<std::tuple<std::string, AZ::Vector2>> m_vec2Params;
        std::vector<std::pair<std::string, bool>> m_boolParams;
        std::vector<std::pair<std::string, float>> m_floatParams;
    };

    struct PerformanceTestParameters
    {
        const char* m_description;
        float m_fps;
        float m_motionSamplingRate;
        float m_totalTestTimeInSeconds;
        size_t m_numInstances;
        size_t m_numSkinAttachmentsPerInstance;
        AZ::u32 m_lodLevel;
        bool m_includeSoftwareSkinning;
        bool m_useMultiThreading;

        void Print() const
        {
            AZ_Printf("EMotionFX", "-------------------------------\n");
            AZ_Printf("EMotionFX", "- Performance Test Parameters -\n");
            AZ_Printf("EMotionFX", "- Description: %s -\n", m_description);
            AZ_Printf("EMotionFX", "- FPS:            %f\n", m_fps);
            AZ_Printf("EMotionFX", "- Motion Sampling Rate:  %f\n", m_motionSamplingRate);
            AZ_Printf("EMotionFX", "- Total Time (s): %f\n", m_totalTestTimeInSeconds);
            const size_t numFrames = static_cast<int>(m_totalTestTimeInSeconds * m_fps);
            AZ_Printf("EMotionFX", "- Frames:         %d\n", numFrames);
            AZ_Printf("EMotionFX", "- Frames:         %d\n", numFrames);
            AZ_Printf("EMotionFX", "- Num Instances:  %d\n", m_numInstances);
            AZ_Printf("EMotionFX", "- Num Skin Attachents per Instance: %d\n", m_numSkinAttachmentsPerInstance);
            AZ_Printf("EMotionFX", "- LOD Level:      %d\n", m_lodLevel);
            AZ_Printf("EMotionFX", "- Software Skin:  %s\n", m_includeSoftwareSkinning ? "True" : "False");
            AZ_Printf("EMotionFX", "- Multi-threading:%s\n", m_useMultiThreading ? "True" : "False");
        }
    };

    class PerformanceTestFixture
        : public SampleGameFixture
        , public ::testing::WithParamInterface<PerformanceTestParameters>
    {
    public:
        void SetUp() override
        {
            SampleGameFixture::SetUp();

            AZ::SerializeContext* serializeContext = nullptr;
            AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
            if (serializeContext)
            {
                Physics::ReflectionUtils::ReflectPhysicsApi(serializeContext);
            }

            m_random = new AZ::SimpleLcgRandom();
            m_random->SetSeed(875960);

            SampleParameterSet params;
            params.m_floatParams.push_back({ "movement_speed", 0.0f });
            params.m_vec2Params.push_back({"movement_direction", AZ::Vector2(0.0f, 0.0f) });
            params.m_boolParams.push_back({"jumping", false});
            params.m_boolParams.push_back({"attacking", true});
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 1.0f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(1.0f, 0.0f) });
            params.m_boolParams.push_back({ "jumping", false });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 0.5f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(0.5f, 0.5f) });
            params.m_boolParams.push_back({ "jumping", false });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);

            params.m_floatParams.push_back({ "movement_speed", 0.0f });
            params.m_vec2Params.push_back({ "movement_direction", AZ::Vector2(0.0f, 0.0f) });
            params.m_boolParams.push_back({ "jumping", true });
            params.m_boolParams.push_back({ "attacking", false });
            m_pyroParameterSet.emplace_back(params);
        }

        void TearDown() override
        {
            delete m_random;
            SampleGameFixture::TearDown();
        }

        float RandomRange(float min, float max) const
        {
            return min + m_random->GetRandomFloat() * (max - min);
        }

        AZ::Vector3 RandomRangeVec3(float min, float max) const
        {
            return AZ::Vector3(
                RandomRange(min, max),
                RandomRange(min, max),
                RandomRange(min, max));
        }

        void RandomizeParameters(const AZStd::vector<ActorInstance*>& actorInstances)
        {
            for (ActorInstance* actorInstance : actorInstances)
            {
                AnimGraphInstance* animGraphInstance = actorInstance->GetAnimGraphInstance();
                AZ_Assert(animGraphInstance, "Actor instance should have an anim graph instance playing.");
                const AnimGraph* animGraph = animGraphInstance->GetAnimGraph();

                const size_t parameterSetIndex = static_cast<size_t>(RandomRange(0.0f, static_cast<float>(m_pyroParameterSet.size())-0.0001f));
                const SampleParameterSet& params = m_pyroParameterSet[parameterSetIndex];

                // Set parameters to default values.
                const size_t numParameters = animGraph->GetNumValueParameters();
                for (size_t i = 0; i < numParameters; ++i)
                {
                    const ValueParameter* parameter = animGraph->FindValueParameter(i);
                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(i));

                    if (parameter->RTTI_GetType() == azrtti_typeid<BoolParameter>())
                    {
                        const BoolParameter* boolParameter = static_cast<const BoolParameter*>(parameter);
                        MCore::AttributeBool* boolAttribute = static_cast<MCore::AttributeBool*>(attribute);
                        boolAttribute->SetValue(boolParameter->GetDefaultValue());
                    }
                    if (parameter->RTTI_GetType() == azrtti_typeid<FloatParameter>())
                    {
                        const FloatParameter* floatParameter = static_cast<const FloatParameter*>(parameter);
                        MCore::AttributeFloat* floatAttribute = static_cast<MCore::AttributeFloat*>(attribute);
                        floatAttribute->SetValue(floatParameter->GetDefaultValue());
                    }
                    if (parameter->RTTI_GetType() == azrtti_typeid<Vector2Parameter>())
                    {
                        const Vector2Parameter* vec2Parameter = static_cast<const Vector2Parameter*>(parameter);
                        MCore::AttributeVector2* vec2Attribute = static_cast<MCore::AttributeVector2*>(attribute);
                        vec2Attribute->SetValue(vec2Parameter->GetDefaultValue());
                    }
                }

                // Bool parameters
                for (const auto& boolParamPair : params.m_boolParams)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(boolParamPair.first.c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), boolParamPair.first.c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const BoolParameter* boolParameter = static_cast<const BoolParameter*>(parameter);
                    MCore::AttributeBool* boolAttribute = static_cast<MCore::AttributeBool*>(attribute);
                    boolAttribute->SetValue(boolParamPair.second);
                }

                // Vec2 parameters
                for (const auto& vec2ParamTuple : params.m_vec2Params)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(std::get<0>(vec2ParamTuple).c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), std::get<0>(vec2ParamTuple).c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const Vector2Parameter* vec2Parameter = static_cast<const Vector2Parameter*>(parameter);
                    MCore::AttributeVector2* vec2Attribute = static_cast<MCore::AttributeVector2*>(attribute);
                    vec2Attribute->SetValue(std::get<1>(vec2ParamTuple));
                }

                // Float parameters
                for (const auto& floatParamPair : params.m_floatParams)
                {
                    const AZ::Outcome<size_t> parameterIndex = animGraph->FindValueParameterIndexByName(floatParamPair.first.c_str());
                    ASSERT_TRUE(parameterIndex.IsSuccess());
                    const ValueParameter* parameter = animGraph->FindValueParameter(parameterIndex.GetValue());
                    ASSERT_NE(parameter, nullptr);
                    ASSERT_EQ(parameter->GetName(), floatParamPair.first.c_str());

                    MCore::Attribute* attribute = animGraphInstance->GetParameterValue(static_cast<AZ::u32>(parameterIndex.GetValue()));
                    const FloatParameter* floatParameter = static_cast<const FloatParameter*>(parameter);
                    MCore::AttributeFloat* floatAttribute = static_cast<MCore::AttributeFloat*>(attribute);
                    floatAttribute->SetValue(floatParamPair.second);
                }
            }
        }

        static AZStd::tuple<float, float, float, float> CalculateStats(const AZStd::vector<float>& samples)
        {
            const size_t numSamples = samples.size();
            if (numSamples == 0)
            {
                return { 0.0f, 0.0f, 0.0f, 0.0f };
            }

            float best = samples[0];
            float worst = samples[0];
            float accumulated = 0.0f;
            for (const float sample : samples)
            {
                best = AZ::GetMin<float>(best, sample);
                worst = AZ::GetMax<float>(worst, sample);
                accumulated += sample;
            }
            const float mean = accumulated / static_cast<float>(numSamples);

            float variance = 0.0f;
            for (const float sample : samples)
            {
                variance += powf(sample - mean, 2.0f);
            }
            variance = variance / static_cast<float>(numSamples);
            const float stdDeviation = sqrtf(variance);

            return { best, mean, worst, stdDeviation };
        }

        void PrintReport(const AZStd::vector<float>& transformUpdateFrameTimes,
            const AZStd::vector<float>& meshDeformFrameTimes,
            float totalTransformUpdateTime,
            float totalMeshDeformTime)
        {
            const PerformanceTestParameters& param = GetParam();

            // Totals
            AZ_Printf("EMotionFX", "----------------------------------------------------\n");
            AZ_Printf("EMotionFX", "- Performance Test Report                          -\n");
            if (param.m_includeSoftwareSkinning)
            {
                AZ_Printf("EMotionFX", "- Totals:\n");
                AZ_Printf("EMotionFX", "    Total Time (s):                  %.4f s\n", totalTransformUpdateTime + totalMeshDeformTime);
                AZ_Printf("EMotionFX", "    Total Transform Update Time (s): %.4f s\n", totalTransformUpdateTime);
                AZ_Printf("EMotionFX", "    Total Mesh Deform Time (s):      %.4f s\n", totalMeshDeformTime);
                AZ_Printf("EMotionFX", "    Transform Mesh Ratio:            %.4f %%\n", totalTransformUpdateTime / totalMeshDeformTime);
            }
            else
            {
                AZ_Printf("EMotionFX", "- Total Time (s):                    %.4f s\n", totalTransformUpdateTime + totalMeshDeformTime);
            }

            // Transform update
            float transformBest;
            float transformMean;
            float transformWorst;
            float transformStdDeviation;
            AZStd::tie(transformBest, transformMean, transformWorst, transformStdDeviation) = CalculateStats(transformUpdateFrameTimes);

            AZ_Printf("EMotionFX", "- Transform update:\n");
            AZ_Printf("EMotionFX", "    Best Frame:                      %.4f ms (%.1f FPS)\n", transformBest * 1000.0f, transformBest > 0.0f ? 1.0f / transformBest : 0.0f);
            AZ_Printf("EMotionFX", "    Mean Frame:                      %.4f ms (%.1f FPS)\n", transformMean * 1000.0f, transformMean > 0.0f ? 1.0f / transformMean : 0.0f);
            AZ_Printf("EMotionFX", "    Worst Frame:                     %.4f ms (%.1f FPS)\n", transformWorst * 1000.0f, transformWorst > 0.0f ? 1.0f / transformWorst : 0.0f);
            AZ_Printf("EMotionFX", "    Std Deviation:                   %.4f ms\n", transformStdDeviation * 1000.0f);

            // Mesh deforms
            if (param.m_includeSoftwareSkinning)
            {
                float meshDeformBest;
                float meshDeformMean;
                float meshDeformWorst;
                float meshDeformStdDeviation;
                AZStd::tie(meshDeformBest, meshDeformMean, meshDeformWorst, meshDeformStdDeviation) = CalculateStats(meshDeformFrameTimes);

                AZ_Printf("EMotionFX", "- Mesh deforms:\n");
                AZ_Printf("EMotionFX", "    Best Frame:                      %.4f ms\n", meshDeformBest * 1000.0f);
                AZ_Printf("EMotionFX", "    Mean Frame:                      %.4f ms\n", meshDeformMean * 1000.0f);
                AZ_Printf("EMotionFX", "    Worst Frame:                     %.4f ms\n", meshDeformWorst * 1000.0f);
                AZ_Printf("EMotionFX", "    Std Deviation:                   %.4f ms\n", meshDeformStdDeviation * 1000.0f);
            }

            AZ_Printf("EMotionFX", "----------------------------------------------------\n");
        }

    private:
        AZ::SimpleLcgRandom* m_random;
        std::vector<SampleParameterSet> m_pyroParameterSet;
    };

    TEST_P(PerformanceTestFixture, PerformanceTest)
    {
        const PerformanceTestParameters& param = GetParam();
        const size_t numIterations = static_cast<size_t>(param.m_totalTestTimeInSeconds * param.m_fps);
        const float frameTimeDelta = 1.0f / param.m_fps;
        param.Print();

        ActorManager* actorManager = GetEMotionFX().GetActorManager();
        ActorUpdateScheduler* scheduler = nullptr;
        if (param.m_useMultiThreading)
        {
            scheduler = MultiThreadScheduler::Create();
        }
        else
        {
            scheduler = SingleThreadScheduler::Create();
        }
        actorManager->SetScheduler(scheduler);

        const AZStd::string assetFolder = GetAssetFolder();
        GetEMotionFX().SetMediaRootFolder(assetFolder.c_str());
        GetEMotionFX().InitAssetFolderPaths();

        // This path points to assets in the advance rin demo.
        // To test different assets, change the path here.
        const char* actorFilename     = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\Actor\\rinActor.actor";
        const char* motionSetFilename = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\AnimationEditorFiles\\Advanced_RinLocomotion.motionset";
        const char* animGraphFilename = "@assets@\\AnimationSamples\\Advanced_RinLocomotion\\AnimationEditorFiles\\Advanced_RinLocomotion.animgraph";

        Importer* importer = GetEMotionFX().GetImporter();
        importer->SetLoggingEnabled(false);

        Actor* actor = importer->LoadActor(ResolvePath(actorFilename));
        ASSERT_NE(actor, nullptr) << "Actor failed to load.";
        MotionSet* motionSet = importer->LoadMotionSet(ResolvePath(motionSetFilename));
        ASSERT_NE(motionSet, nullptr) << "Motion set failed to load.";

        AnimGraph* animGraph = importer->LoadAnimGraph(ResolvePath(animGraphFilename));
        ASSERT_NE(animGraph, nullptr) << "Anim graph failed to load.";

        // Create instances and start running the anim graphs.
        AZStd::vector<ActorInstance*> actorInstances;
        AZStd::vector<ActorInstance*> actorInstancesIncludingAttachments;
        actorInstances.reserve(param.m_numInstances);
        for (size_t i = 0; i < param.m_numInstances; ++i)
        {
            ActorInstance* actorInstance = ActorInstance::Create(actor);
            if (!AZ::IsClose(param.m_motionSamplingRate, 0.0f, AZ::g_fltEps))
            {
                actorInstance->SetMotionSamplingRate(1.0f / param.m_motionSamplingRate);
            }
            actorInstance->SetLocalSpacePosition(RandomRangeVec3(-100.0f, 100.0f));
            actorInstances.emplace_back(actorInstance);
            actorInstancesIncludingAttachments.emplace_back(actorInstance);

            AnimGraphInstance* animGraphInstance = AnimGraphInstance::Create(animGraph, actorInstance, motionSet);
            actorInstance->SetAnimGraphInstance(animGraphInstance);

            actorInstance->SetLODLevel(param.m_lodLevel);
            actorInstance->UpdateTransformations(0.0f);

            // Add skin attachments.
            for (size_t attachmentNr = 0; attachmentNr < param.m_numSkinAttachmentsPerInstance; ++attachmentNr)
            {
                ActorInstance* attachmentActorInstance = ActorInstance::Create(actor);
                EMotionFX::Attachment* attachment = EMotionFX::AttachmentSkin::Create(/*attachmentTarget*/actorInstance, attachmentActorInstance);
                actorInstance->AddAttachment(attachment);
                actorInstancesIncludingAttachments.emplace_back(attachmentActorInstance);
            }
        }

        // Preload motions and make sure they got loaded successfully.
        motionSet->Preload();
        const auto& motionEntries = motionSet->GetMotionEntries();
        for (const auto& motionEntryPair : motionEntries)
        {
            const MotionSet::MotionEntry* motionEntry = motionEntryPair.second;
            ASSERT_NE(motionEntry->GetMotion(), nullptr);
        }

        AZ::Debug::Timer timer;
        AZStd::vector<float> transformUpdateFrameTimes;
        AZStd::vector<float> meshDeformFrameTimes;
        transformUpdateFrameTimes.reserve(numIterations);
        meshDeformFrameTimes.reserve(numIterations);
        float totalTransformUpdateTime = 0.0f;
        float totalMeshDeformTime = 0.0f;

        const float randomizeParametersEvery = 1.0; // Change parameters every second
        float randomizeParameterTimer = 0.0f;

        for (size_t i = 0; i < numIterations; ++i)
        {
            randomizeParameterTimer += frameTimeDelta;
            if (randomizeParameterTimer >= randomizeParametersEvery)
            {
                RandomizeParameters(actorInstances);
                randomizeParameterTimer = 0.0;
            }

            // Output skeletal poses.
            timer.Stamp();
            GetEMotionFX().Update(frameTimeDelta);
            const float transformUpdateTime = timer.GetDeltaTimeInSeconds();
            totalTransformUpdateTime += transformUpdateTime;
            transformUpdateFrameTimes.emplace_back(transformUpdateTime);

            // Update mesh deformers (software skinning).
            if (param.m_includeSoftwareSkinning)
            {
                timer.Stamp();
                for (ActorInstance* actorInstance : actorInstancesIncludingAttachments)
                {
                    actorInstance->UpdateMeshDeformers(frameTimeDelta);
                }
                const float meshDeformTime = timer.GetDeltaTimeInSeconds();
                totalMeshDeformTime += meshDeformTime;
                meshDeformFrameTimes.emplace_back(meshDeformTime);
            }
        }

        PrintReport(transformUpdateFrameTimes, meshDeformFrameTimes, totalTransformUpdateTime, totalMeshDeformTime);

        for (ActorInstance* actorInstance : actorInstancesIncludingAttachments)
        {
            actorInstance->Destroy();
        }
        actor->Destroy();
        delete animGraph;
        delete motionSet;
    }

    const float g_updatesPerSec = 60.0f;
    const float g_totalTestTime = 60.0f; // 1 minute
    const size_t g_numInstances = 100;

    std::vector<PerformanceTestParameters> performanceTestData
    {
        // Baseline
        {
            "Baseline",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            false // multi-threading
        },
        // Multi-threading
        {
            "Multi-threading",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading with 1 skin attachments
        {
            "Multi-threading with 1 skin attachments",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            1, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading and restricted motion sampling rate
        {
            "Multi-threading and restricted motion sampling rate",
            g_updatesPerSec, // fps
            60.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading and lower motion sampling rate
        {
            "Multi-threading and lower motion sampling rate",
            g_updatesPerSec, // fps
            30.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading and lower motion sampling rate
        {
            "Multi-threading and lower motion sampling rate",
            g_updatesPerSec, // fps
            10.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading at LOD level = 1
        {
            "Multi-threading at LOD level = 1",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            1, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading at LOD level = 2
        {
            "Multi-threading at LOD level = 2",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            2, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading at LOD level = 3
        {
            "Multi-threading at LOD level = 3",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            3, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Multi-threading at LOD level = 4
        {
            "Multi-threading at LOD level = 4",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            4, // lod level
            false, // software skinning
            true // multi-threading
        },
        // Server test: LOD0
        {
            "Server test: LOD0",
            30.0f, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            false // multi-threading
        },
        // Server test: LOD4
        {
            "Server test: LOD4",
            30.0f, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            4, // lod level
            false, // software skinning
            false // multi-threading
        },
        // Server test: Server actor optimization (bone removal).
        {
            "Server test: Server actor optimization (bone removal)",
            30.0f, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            false // multi-threading
        },
        // Software skinning
        {
            "Software skinning",
            g_updatesPerSec, // fps
            0.0f, // motion sampling rate
            g_totalTestTime, // total test time
            g_numInstances, // instances
            0, // num skin attachments per instance
            0, // lod level
            true, // software skinning
            false // multi-threading
        },
    };

    std::vector<PerformanceTestParameters> debugTestData
    {
        {
            "debug",
            g_updatesPerSec, // fps
            60.0f, // motion sampling rate
            g_totalTestTime, // total test time
            1, // instances
            0, // num skin attachments per instance
            0, // lod level
            false, // software skinning
            true // multi-threading
        }
    };

    INSTANTIATE_TEST_CASE_P(DISABLED_PerformanceTests,
        PerformanceTestFixture,
        ::testing::ValuesIn(performanceTestData));
} // namespace EMotionFX
