/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

// Description : CryENGINE system core


#include "StdAfx.h"
#include "System.h"
#include "IEntity.h"
#include "IStatoscope.h"

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#include <IRenderer.h>
#include <IRenderAuxGeom.h>
#include <IProcess.h>
#include "Log.h"
#include "XConsole.h"
#include <I3DEngine.h>
#include <IAISystem.h>
#include <CryLibrary.h>
#include <IBudgetingSystem.h>
#include "PhysRenderer.h"
#include <IScriptSystem.h>
#include <IEntitySystem.h>
#include <IParticles.h>
#include <IMovieSystem.h>
#include <IPlatformOS.h>

#include "CrySizerStats.h"
#include "CrySizerImpl.h"
#include "ITestSystem.h"                            // ITestSystem
#include "VisRegTest.h"
#include "ThreadProfiler.h"
#include "IDiskProfiler.h"
#include "ITextModeConsole.h"
#include <IEntitySystem.h> // <> required for Interfuscator
#include <IGame.h>
#include <ILevelSystem.h>
#include <LyShine/ILyShine.h>

#include "MiniGUI/MiniGUI.h"
#include "PerfHUD.h"
#include "ThreadInfo.h"

#include <LoadScreenBus.h>

#if defined(AZ_RESTRICTED_PLATFORM)
#undef AZ_RESTRICTED_SECTION
#define SYSTEMRENDERER_CPP_SECTION_1 1
#define SYSTEMRENDERER_CPP_SECTION_2 2
#endif

extern CMTSafeHeap* g_pPakHeap;
#if defined(AZ_PLATFORM_ANDROID)
#include <AzCore/Android/Utils.h>
#elif defined(AZ_PLATFORM_IOS) || defined(AZ_PLATFORM_APPLE_TV)
extern bool UIKitGetPrimaryPhysicalDisplayDimensions(int& o_widthPixels, int& o_heightPixels);
extern bool UIDeviceIsTablet();
#endif

extern int CryMemoryGetAllocatedSize();

/////////////////////////////////////////////////////////////////////////////////
static void VerifySizeRenderVar(ICVar* pVar)
{
    const int size = pVar->GetIVal();
    if (size <= 0)
    {
        AZ_Error("Console Variable", false, "'%s' set to invalid value: %i. Setting to nearest safe value: 1.", pVar->GetName(), size);
        pVar->Set(1);
    }
}

/////////////////////////////////////////////////////////////////////////////////
bool CSystem::GetPrimaryPhysicalDisplayDimensions(int& o_widthPixels, int& o_heightPixels)
{
#if defined(AZ_PLATFORM_WINDOWS)
    o_widthPixels = GetSystemMetrics(SM_CXSCREEN);
    o_heightPixels = GetSystemMetrics(SM_CYSCREEN);
    return true;
#elif defined(AZ_PLATFORM_ANDROID)
    return AZ::Android::Utils::GetWindowSize(o_widthPixels, o_heightPixels);
#elif defined(AZ_PLATFORM_IOS) || defined(AZ_PLATFORM_APPLE_TV)
    return UIKitGetPrimaryPhysicalDisplayDimensions(o_widthPixels, o_heightPixels);
#else
    return false;
#endif
}

bool CSystem::IsTablet()
{
//TODO: Add support for Android tablets
#if defined(AZ_PLATFORM_IOS) || defined(AZ_PLATFORM_APPLE_TV)
    return UIDeviceIsTablet();
#else
    return false;
#endif
}

/////////////////////////////////////////////////////////////////////////////////
void CSystem::CreateRendererVars(const SSystemInitParams& startupParams)
{
    int iFullScreenDefault = 1;
    int iDisplayInfoDefault = 0;
    int iWidthDefault = 1280;
    int iHeightDefault = 720;
#if defined(AZ_PLATFORM_ANDROID) || defined(AZ_PLATFORM_IOS) || defined(AZ_PLATFORM_APPLE_TV)
    GetPrimaryPhysicalDisplayDimensions(iWidthDefault, iHeightDefault);
#elif defined(WIN32) || defined(WIN64)
    iFullScreenDefault = 0;
    iWidthDefault = GetSystemMetrics(SM_CXFULLSCREEN) * 2 / 3;
    iHeightDefault = GetSystemMetrics(SM_CYFULLSCREEN) * 2 / 3;
#endif

    if (IsDevMode())
    {
        iFullScreenDefault = 0;
        iDisplayInfoDefault = 1;
    }

    // load renderer settings from engine.ini
    m_rWidth = REGISTER_INT_CB("r_Width", iWidthDefault, VF_DUMPTODISK,
            "Sets the display width, in pixels. Default is 1280.\n"
            "Usage: r_Width [800/1024/..]", VerifySizeRenderVar);
    m_rHeight = REGISTER_INT_CB("r_Height", iHeightDefault, VF_DUMPTODISK,
            "Sets the display height, in pixels. Default is 720.\n"
            "Usage: r_Height [600/768/..]", VerifySizeRenderVar);
    m_rWidthAndHeightAsFractionOfScreenSize = REGISTER_FLOAT("r_WidthAndHeightAsFractionOfScreenSize", 1.0f, VF_DUMPTODISK,
            "(iOS/Android only) Sets the display width and height as a fraction of the physical screen size. Default is 1.0.\n"
            "Usage: rWidthAndHeightAsFractionOfScreenSize [0.1 - 1.0]");
    m_rTabletWidthAndHeightAsFractionOfScreenSize = REGISTER_FLOAT("r_TabletWidthAndHeightAsFractionOfScreenSize", 1.0f, VF_DUMPTODISK,
            "(iOS only) NOTE: TABLETS ONLY Sets the display width and height as a fraction of the physical screen size. Default is 1.0.\n"
            "Usage: rTabletWidthAndHeightAsFractionOfScreenSize [0.1 - 1.0]");
    m_rMaxWidth = REGISTER_INT("r_MaxWidth", 0, VF_DUMPTODISK,
            "(iOS/Android only) Sets the maximum display width while maintaining the device aspect ratio.\n"
            "Usage: r_MaxWidth [1024/1920/..] (0 for no max), combined with r_WidthAndHeightAsFractionOfScreenSize [0.1 - 1.0]");
    m_rMaxHeight = REGISTER_INT("r_MaxHeight", 0, VF_DUMPTODISK,
            "(iOS/Android only) Sets the maximum display height while maintaining the device aspect ratio.\n"
            "Usage: r_MaxHeight [768/1080/..] (0 for no max), combined with r_WidthAndHeightAsFractionOfScreenSize [0.1 - 1.0]");
    m_rColorBits = REGISTER_INT("r_ColorBits", 32, VF_DUMPTODISK,
            "Sets the color resolution, in bits per pixel. Default is 32.\n"
            "Usage: r_ColorBits [32/24/16/8]");
    m_rDepthBits = REGISTER_INT("r_DepthBits", 24, VF_DUMPTODISK | VF_REQUIRE_APP_RESTART,
            "Sets the depth precision, in bits per pixel. Default is 24.\n"
            "Usage: r_DepthBits [32/24]");
    m_rStencilBits = REGISTER_INT("r_StencilBits", 8, VF_DUMPTODISK,
            "Sets the stencil precision, in bits per pixel. Default is 8.\n");

    // Needs to be initialized as soon as possible due to swap chain creation modifications..
    m_rHDRDolby = REGISTER_INT_CB("r_HDRDolby", 0, VF_DUMPTODISK,
            "HDR dolby output mode\n"
            "Usage: r_HDRDolby [Value]\n"
            "0: Off (default)\n"
            "1: Dolby maui output\n"
            "2: Dolby vision output\n",
            [] (ICVar* cvar)
            {
                eDolbyVisionMode mode = static_cast<eDolbyVisionMode>(cvar->GetIVal());

                if (mode == eDVM_Vision && gEnv->IsEditor())
                {
                    cvar->Set(static_cast<int>(eDVM_Disabled));
                }
            });
    // Restrict the limits of this cvar to the eDolbyVisionMode values
    m_rHDRDolby->SetLimits(static_cast<float>(eDVM_Disabled), static_cast<float>(eDVM_Vision));

#if defined(WIN32) || defined(WIN64)
    REGISTER_INT("r_overrideDXGIAdapter", -1, VF_REQUIRE_APP_RESTART,
        "Specifies index of the preferred video adapter to be used for rendering (-1=off, loops until first suitable adapter is found).\n"
        "Use this to resolve which video card to use if more than one DX11 capable GPU is available in the system.");
#endif

#if defined(WIN32) || defined(WIN64)
    const char* p_r_DriverDef = "Auto";
#elif defined(APPLE)
    const char* p_r_DriverDef = "METAL";
#elif defined(ANDROID)
    const char* p_r_DriverDef = "GL";
#elif defined(LINUX)
    const char* p_r_DriverDef = "GL";
    if (gEnv->IsDedicated())
    {
        p_r_DriverDef = "NULL";
    }
#elif defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEMRENDERER_CPP_SECTION_1
    #if defined(AZ_PLATFORM_XENIA)
        #include "Xenia/SystemRender_cpp_xenia.inl"
    #elif defined(AZ_PLATFORM_PROVO)
        #include "Provo/SystemRender_cpp_provo.inl"
    #elif defined(AZ_PLATFORM_SALEM)
        #include "Salem/SystemRender_cpp_salem.inl"
    #endif
#else
    const char* p_r_DriverDef = "DX9";                          // required to be deactivated for final release
#endif
    if (startupParams.pCvarsDefault)
    { // hack to customize the default value of r_Driver
        SCvarsDefault* pCvarsDefault = startupParams.pCvarsDefault;
        if (pCvarsDefault->sz_r_DriverDef && pCvarsDefault->sz_r_DriverDef[0])
        {
            p_r_DriverDef = startupParams.pCvarsDefault->sz_r_DriverDef;
        }
    }

    m_rDriver = REGISTER_STRING("r_Driver", p_r_DriverDef, VF_DUMPTODISK | VF_INVISIBLE,
            "Sets the renderer driver ( DX11/AUTO/NULL ).\n"
            "Specify in bootstrap.cfg like this: r_Driver = \"DX11\"");

    m_rFullscreen = REGISTER_INT("r_Fullscreen", iFullScreenDefault, VF_DUMPTODISK,
            "Toggles fullscreen mode. Default is 1 in normal game and 0 in DevMode.\n"
            "Usage: r_Fullscreen [0=window/1=fullscreen]");

    m_rFullscreenWindow = REGISTER_INT("r_FullscreenWindow", 0, VF_DUMPTODISK,
            "Toggles fullscreen-as-window mode. Fills screen but allows seamless switching. Default is 0.\n"
            "Usage: r_FullscreenWindow [0=locked fullscreen/1=fullscreen as window]");

    m_rFullscreenNativeRes = REGISTER_INT("r_FullscreenNativeRes", 0, VF_DUMPTODISK, "");

    m_rDisplayInfo = REGISTER_INT("r_DisplayInfo", iDisplayInfoDefault, VF_RESTRICTEDMODE | VF_DUMPTODISK,
            "Toggles debugging information display.\n"
            "Usage: r_DisplayInfo [0=off/1=show/2=enhanced/3=compact]");

    m_rOverscanBordersDrawDebugView = REGISTER_INT("r_OverscanBordersDrawDebugView", 0, VF_RESTRICTEDMODE | VF_DUMPTODISK,
            "Toggles drawing overscan borders.\n"
            "Usage: r_OverscanBordersDrawDebugView [0=off/1=show]");
}

//////////////////////////////////////////////////////////////////////////
void CSystem::RenderBegin()
{
    FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_SYSTEM, g_bProfilerEnabled);

    if (m_bIgnoreUpdates)
    {
        return;
    }

    bool rndAvail = m_env.pRenderer != 0;

    //////////////////////////////////////////////////////////////////////
    //start the rendering pipeline
    if (rndAvail)
    {
        m_env.pRenderer->BeginFrame();
    }

    gEnv->nMainFrameID = (rndAvail) ? m_env.pRenderer->GetFrameID(false) : 0;
}

#if ENABLE_CRY_PHYSICS
char* PhysHelpersToStr(int iHelpers, char* strHelpers);
int StrToPhysHelpers(const char* strHelpers);
#endif

//////////////////////////////////////////////////////////////////////////
void CSystem::RenderEnd(bool bRenderStats, bool bMainWindow)
{
    {
        FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_SYSTEM, g_bProfilerEnabled);

        if (m_bIgnoreUpdates)
        {
            return;
        }

        if (!m_env.pRenderer)
        {
            return;
        }

        /*
                if(m_env.pMovieSystem)
                    m_env.pMovieSystem->Render();
        */

        GetPlatformOS()->RenderEnd();

        if (m_env.pGame && bMainWindow)
        {
            m_env.pGame->RenderGameWarnings();
        }

        if (bMainWindow)    // we don't do this in UI Editor window for example
        {
#if !defined (_RELEASE)
            // Flush render data and swap buffers.
            m_env.pRenderer->RenderDebug(bRenderStats);
#endif

#if defined(USE_PERFHUD)
            if (m_pPerfHUD)
            {
                m_pPerfHUD->Draw();
            }
            if (m_pMiniGUI)
            {
                m_pMiniGUI->Draw();
            }
#endif
            //If this is called from the launcher / game mode, render the statistic here.
            //If it is editor mode then we want the editor RenderViewport to handle drawing statistics so that
            //other renderable windows won't steal the text render events.
            if (bRenderStats && (!gEnv->IsEditor() || gEnv->IsEditorGameMode()))
            {
                RenderStatistics();
            }

            if (!gEnv->pSystem->GetILevelSystem() || !gEnv->pSystem->GetILevelSystem()->IsLevelLoaded())
            {
                IConsole* console = GetIConsole();
                ILyShine* lyShine = gEnv->pLyShine;

                //Normally the UI is drawn as part of the scene so it can properly render once per eye in VR
                //We only want to draw here if there is no level loaded. This way the user can see loading
                // UI and other information before the level is loaded.
                if (lyShine != nullptr)
                {
                    lyShine->Render();
                }

                //Same goes for the console. When no level is loaded, it's okay to render it outside of the renderer
                //so that users can load maps or change settings.
                if (console != nullptr)
                {
                    console->Draw();
                }
            }
        }

        m_env.pRenderer->ForceGC(); // XXX Rename this
        m_env.pRenderer->EndFrame();


        if (IConsole* pConsole = GetIConsole())
        {
            // if we have pending cvar calculation, execute it here
            // since we know cvars will be correct here after ->EndFrame().
            if (!pConsole->IsHashCalculated())
            {
                pConsole->CalcCheatVarHash();
            }
        }
    }

#if defined(USE_FRAME_PROFILER)
    gEnv->bProfilerEnabled = m_sys_profile->GetIVal() != 0;
#endif
}

void CSystem::OnScene3DEnd()
{
    // Render UI Canvas
    if (m_bDrawUI && gEnv->pLyShine)
    {
        gEnv->pLyShine->Render();
    }

    //Render Console
    if (m_bDrawConsole && gEnv->pConsole)
    {
        gEnv->pConsole->Draw();
    }
}

#if ENABLE_CRY_PHYSICS
//////////////////////////////////////////////////////////////////////////
void CSystem::RenderPhysicsHelpers()
{
#if !defined (_RELEASE)
    if (gEnv->pPhysicalWorld)
    {
        char str[128];
        if (StrToPhysHelpers(m_p_draw_helpers_str->GetString()) != m_env.pPhysicalWorld->GetPhysVars()->iDrawHelpers)
        {
            m_p_draw_helpers_str->Set(PhysHelpersToStr(m_env.pPhysicalWorld->GetPhysVars()->iDrawHelpers, str));
        }

        m_pPhysRenderer->UpdateCamera(GetViewCamera());
        m_pPhysRenderer->DrawAllHelpers(m_env.pPhysicalWorld);
        m_pPhysRenderer->Flush(m_Time.GetFrameTime());

        RenderPhysicsStatistics(gEnv->pPhysicalWorld);
    }
#endif
}

//////////////////////////////////////////////////////////////////////////
void CSystem::RenderPhysicsStatistics(IPhysicalWorld* pWorld)
{
#if !defined(_RELEASE)
    //////////////////////////////////////////////////////////////////////
    // draw physics helpers
    if (pWorld)
    {
        ITextModeConsole* pTMC = GetITextModeConsole();
        phys_profile_info* pInfos;
        phys_job_info* pJobInfos;
        PhysicsVars* pVars;
        int i = -2;
        char msgbuf[512];
        if ((pVars = pWorld->GetPhysVars())->bProfileEntities == 1)
        {
            pe_status_pos sp;
            int j, mask, nEnts = pWorld->GetEntityProfileInfo(pInfos);
            float fColor[4] = { 0.3f, 0.6f, 1.0f, 1.0f }, dt;
            if (!pVars->bSingleStepMode)
            {
                for (i = 0; i < nEnts; i++)
                {
                    pInfos[i].nTicksAvg = (int)(((int64)pInfos[i].nTicksAvg * 15 + pInfos[i].nTicksLast) >> 4);
                    pInfos[i].nCallsAvg = pInfos[i].nCallsAvg * (15.0f / 16) + pInfos[i].nCallsLast * (1.0f / 16);
                }
                phys_profile_info ppi;
                for (i = 0; i < nEnts - 1; i++)
                {
                    for (j = nEnts - 1; j > i; j--)
                    {
                        if (pInfos[j - 1].nTicksAvg < pInfos[j].nTicksAvg)
                        {
                            ppi = pInfos[j - 1];
                            pInfos[j - 1] = pInfos[j];
                            pInfos[j] = ppi;
                        }
                    }
                }
            }
            for (i = 0; i < nEnts; i++)
            {
                mask = (pInfos[i].nTicksPeak - pInfos[i].nTicksLast) >> 31;
                mask |= (70 - pInfos[i].peakAge) >> 31;
                mask &= (pVars->bSingleStepMode - 1) >> 31;
                pInfos[i].nTicksPeak += pInfos[i].nTicksLast - pInfos[i].nTicksPeak & mask;
                pInfos[i].nCallsPeak += pInfos[i].nCallsLast - pInfos[i].nCallsPeak & mask;
                azsprintf(msgbuf, "%.2fms/%.1f (peak %.2fms/%d) %s (id %d)",
                    dt = gEnv->pTimer->TicksToSeconds(pInfos[i].nTicksAvg) * 1000.0f, pInfos[i].nCallsAvg,
                    gEnv->pTimer->TicksToSeconds(pInfos[i].nTicksPeak) * 1000.0f, pInfos[i].nCallsPeak,
                    pInfos[i].pName, pInfos[i].id);
                GetIRenderer()->Draw2dLabel(10.0f, 60.0f + i * 12.0f, 1.3f, fColor, false, "%s", msgbuf);
                if (pTMC)
                {
                    pTMC->PutText(0, i, msgbuf);
                }
                IPhysicalEntity* pent = pWorld->GetPhysicalEntityById(pInfos[i].id);
                if (dt > 0.1f && pent && pent->GetStatus(&sp))
                {
                    GetIRenderer()->DrawLabelEx(sp.pos + Vec3(0, 0, sp.BBox[1].z), 1.4f, fColor, true, true, "%s %.2fms", pInfos[i].pName, dt);
                }
                pInfos[i].peakAge = pInfos[i].peakAge + 1 & ~mask;
                //pInfos[i].nCalls=pInfos[i].nTicks = 0;
            }

            if (inrange(m_iJumpToPhysProfileEnt, 0, nEnts + 1))
            {
                ScriptHandle hdl;
                hdl.n = ~0;
                if (m_env.pScriptSystem)
                {
                    m_env.pScriptSystem->GetGlobalValue("g_localActorId", hdl);
                }
                IEntity* pPlayerEnt = m_env.pEntitySystem ? m_env.pEntitySystem->GetEntity((EntityId)hdl.n) : nullptr;
                IPhysicalEntity* pent = pWorld->GetPhysicalEntityById(pInfos[m_iJumpToPhysProfileEnt - 1].id);
                if (pPlayerEnt && pent)
                {
                    pe_params_bbox pbb;
                    pent->GetParams(&pbb);
                    pPlayerEnt->SetPos((pbb.BBox[0] + pbb.BBox[1]) * 0.5f + Vec3(0, -3.0f, 1.5f));
                    pPlayerEnt->SetRotation(Quat(IDENTITY));
                }
                m_iJumpToPhysProfileEnt = 0;
            }
        }
        if (pVars->bProfileFunx)
        {
            int j, mask, nFunx = pWorld->GetFuncProfileInfo(pInfos);
            float fColor[4] = { 0.75f, 0.08f, 0.85f, 1.0f };
            for (j = 0, ++i; j < nFunx; j++, i++)
            {
                mask = (pInfos[j].nTicksPeak - pInfos[j].nTicks) >> 31;
                mask |= (70 - pInfos[j].peakAge) >> 31;
                pInfos[j].nTicksPeak += pInfos[j].nTicks - pInfos[j].nTicksPeak & mask;
                pInfos[j].nCallsPeak += pInfos[j].nCalls - pInfos[j].nCallsPeak & mask;
                GetIRenderer()->Draw2dLabel(10.0f, 60.0f + i * 12.0f, 1.3f, fColor, false,
                    "%s %.2fms/%d (peak %.2fms/%d)", pInfos[j].pName, gEnv->pTimer->TicksToSeconds(pInfos[j].nTicks) * 1000.0f, pInfos[j].nCalls,
                    gEnv->pTimer->TicksToSeconds(pInfos[j].nTicksPeak) * 1000.0f, pInfos[j].nCallsPeak);
                pInfos[j].peakAge = pInfos[j].peakAge + 1 & ~mask;
                pInfos[j].nCalls = pInfos[j].nTicks = 0;
            }
        }
        if (pVars->bProfileGroups)
        {
            int j = 0, mask, nGroups = pWorld->GetGroupProfileInfo(pInfos), nJobs = pWorld->GetJobProfileInfo(pJobInfos);
            float fColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
            if (!pVars->bProfileEntities)
            {
                j = 12;
            }

            for (++i; j < nGroups; j++, i++)
            {
                pInfos[j].nTicksAvg = (int)(((int64)pInfos[j].nTicksAvg * 15 + pInfos[j].nTicksLast) >> 4);
                mask = (pInfos[j].nTicksPeak - pInfos[j].nTicksLast) >> 31;
                mask |= (70 - pInfos[j].peakAge) >> 31;
                pInfos[j].nTicksPeak += pInfos[j].nTicksLast - pInfos[j].nTicksPeak & mask;
                pInfos[j].nCallsPeak += pInfos[j].nCallsLast - pInfos[j].nCallsPeak & mask;
                float time = gEnv->pTimer->TicksToSeconds(pInfos[j].nTicksAvg) * 1000.0f;
                float timeNorm = time * (1.0f / 32);
                fColor[1] = fColor[2] = 1.0f - (max(0.7f, min(1.0f, timeNorm)) - 0.7f) * (1.0f / 0.3f);
                GetIRenderer()->Draw2dLabel(10.0f, 60.0f + i * 12.0f, 1.3f, fColor, false,
                    "%s %.2fms/%d (peak %.2fms/%d)", pInfos[j].pName, time, pInfos[j].nCallsLast,
                    gEnv->pTimer->TicksToSeconds(pInfos[j].nTicksPeak) * 1000.0f, pInfos[j].nCallsPeak);
                pInfos[j].peakAge = pInfos[j].peakAge + 1 & ~mask;
                if (j == nGroups - 4)
                {
                    ++i;
                }
            }
        }
        if (pVars->bProfileEntities == 2)
        {
            int nEnts = m_env.pPhysicalWorld->GetEntityProfileInfo(pInfos);

            for (int j = 0; j < nEnts; j++)
            {
                gEnv->pStatoscope->AddPhysEntity(&pInfos[j]);
            }
        }
    }
#endif
}
#endif // ENABLE_CRY_PHYSICS

//! Update screen and call some important tick functions during loading.
void CSystem::SynchronousLoadingTick(const char* pFunc, int line)
{
    LOADING_TIME_PROFILE_SECTION;
    if (gEnv && gEnv->bMultiplayer && !gEnv->IsEditor())
    {
        //UpdateLoadingScreen currently contains a couple of tick functions that need to be called regularly during the synchronous level loading,
        //when the usual engine and game ticks are suspended.
        UpdateLoadingScreen();

#if defined(MAP_LOADING_SLICING)
        GetISystemScheduler()->SliceAndSleep(pFunc, line);
#endif
    }
}


//////////////////////////////////////////////////////////////////////////
void CSystem::UpdateLoadingScreen()
{
    // Do not update the network thread from here - it will cause context corruption - use the NetworkStallTicker thread system

    if (GetCurrentThreadId() != gEnv->mMainThreadId)
    {
        return;
    }

#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION SYSTEMRENDERER_CPP_SECTION_2
    #if defined(AZ_PLATFORM_XENIA)
        #include "Xenia/SystemRender_cpp_xenia.inl"
    #elif defined(AZ_PLATFORM_PROVO)
        #include "Provo/SystemRender_cpp_provo.inl"
    #elif defined(AZ_PLATFORM_SALEM)
        #include "Salem/SystemRender_cpp_salem.inl"
    #endif
#endif

#if AZ_LOADSCREENCOMPONENT_ENABLED
    EBUS_EVENT(LoadScreenBus, UpdateAndRender);
#endif // if AZ_LOADSCREENCOMPONENT_ENABLED

    if (!m_bEditor && !IsQuitting())
    {
        if (m_pProgressListener)
        {
            m_pProgressListener->OnLoadingProgress(0);
        }
    }
}

//////////////////////////////////////////////////////////////////////////

void CSystem::DisplayErrorMessage(const char* acMessage,
    float fTime,
    const float* pfColor,
    bool bHardError)
{
    SErrorMessage message;
    message.m_Message = acMessage;
    if (pfColor)
    {
        memcpy(message.m_Color, pfColor, 4 * sizeof(float));
    }
    else
    {
        message.m_Color[0] = 1.0f;
        message.m_Color[1] = 0.0f;
        message.m_Color[2] = 0.0f;
        message.m_Color[3] = 1.0f;
    }
    message.m_HardFailure = bHardError;
#ifdef _RELEASE
    message.m_fTimeToShow = fTime;
#else
    message.m_fTimeToShow = 1.0f;
#endif
    m_ErrorMessages.push_back(message);
}

//! Renders the statistics; this is called from RenderEnd, but if the
//! Host application (Editor) doesn't employ the Render cycle in ISystem,
//! it may call this method to render the essential statistics
//////////////////////////////////////////////////////////////////////////
void CSystem::RenderStatistics()
{
    RenderStats();
#if defined(USE_FRAME_PROFILER)
    // Render profile info.
    m_FrameProfileSystem.Render();
    if (m_pThreadProfiler)
    {
        m_pThreadProfiler->Render();
    }

#if defined(USE_DISK_PROFILER)
    if (m_pDiskProfiler)
    {
        m_pDiskProfiler->Update();
    }
#endif

    RenderMemStats();

    if (m_sys_profile_sampler->GetIVal() > 0)
    {
        m_sys_profile_sampler->Set(0);
        m_FrameProfileSystem.StartSampling(m_sys_profile_sampler_max_samples->GetIVal());
    }

    // Update frame profiler from sys variable: 1 = enable and display, -1 = just enable
    int profValue = m_sys_profile->GetIVal();
    static int prevProfValue = -100;
    bool bEnable = profValue != 0,
         bDisplay = profValue > 0;
    if (prevProfValue != profValue)
    {
        prevProfValue = profValue;
        int dispNum = abs(profValue);
        m_FrameProfileSystem.SetDisplayQuantity((CFrameProfileSystem::EDisplayQuantity)(dispNum - 1));
    }
    if (bEnable != m_FrameProfileSystem.IsEnabled() || bDisplay != m_FrameProfileSystem.IsVisible())
    {
        m_FrameProfileSystem.Enable(bEnable, bDisplay);
    }
    if (m_FrameProfileSystem.IsEnabled())
    {
        static string sSysProfileFilter;
        if (azstricmp(m_sys_profile_filter->GetString(), sSysProfileFilter.c_str()) != 0)
        {
            sSysProfileFilter = m_sys_profile_filter->GetString();
            m_FrameProfileSystem.SetSubsystemFilter(sSysProfileFilter.c_str());
        }
        static string sSysProfileFilterThread;
        if (0 != sSysProfileFilterThread.compare(m_sys_profile_filter_thread->GetString()))
        {
            sSysProfileFilterThread = m_sys_profile_filter_thread->GetString();
            m_FrameProfileSystem.SetSubsystemFilterThread(sSysProfileFilterThread.c_str());
            m_sys_profile_allThreads->Set(1);
        }
        m_FrameProfileSystem.SetHistogramScale(m_sys_profile_graphScale->GetFVal());
        m_FrameProfileSystem.SetDrawGraph(m_sys_profile_graph->GetIVal() != 0);
        m_FrameProfileSystem.SetThreadSupport(m_sys_profile_allThreads->GetIVal());
        m_FrameProfileSystem.SetNetworkProfiler(m_sys_profile_network->GetIVal() != 0);
        m_FrameProfileSystem.SetPeakTolerance(m_sys_profile_peak->GetFVal());
        m_FrameProfileSystem.SetPageFaultsGraph(m_sys_profile_pagefaultsgraph->GetIVal() != 0);
        m_FrameProfileSystem.SetPeakDisplayDuration(m_sys_profile_peak_time->GetFVal());
        m_FrameProfileSystem.SetAdditionalSubsystems(m_sys_profile_additionalsub->GetIVal() != 0);
    }
    static int memProfileValueOld = 0;
    int memProfileValue = m_sys_profile_memory->GetIVal();
    if (memProfileValue != memProfileValueOld)
    {
        memProfileValueOld = memProfileValue;
        m_FrameProfileSystem.EnableMemoryProfile(memProfileValue != 0);
    }
#endif

    RenderThreadInfo();
#if !defined(_RELEASE)
    if (m_sys_enable_budgetmonitoring->GetIVal())
    {
        m_pIBudgetingSystem->MonitorBudget();
    }
#endif
}

//////////////////////////////////////////////////////////////////////
void CSystem::Render()
{
    if (m_bIgnoreUpdates)
    {
        return;
    }

    //check what is the current process
    if (!m_pProcess)
    {
        return; //should never happen
    }
    //check if the game is in pause or
    //in menu mode
    //bool bPause=false;
    //if (m_pProcess->GetFlags() & PROC_MENU)
    //  bPause=true;

    FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_SYSTEM, g_bProfilerEnabled);

    //////////////////////////////////////////////////////////////////////
    //draw
    m_env.p3DEngine->PreWorldStreamUpdate(m_ViewCamera);

    if (m_pProcess)
    {
        if (m_pProcess->GetFlags() & PROC_3DENGINE)
        {
            if (!gEnv->IsEditing())  // Editor calls it's own rendering update
            {
#if !defined(_RELEASE)
                if (m_pTestSystem)
                {
                    m_pTestSystem->BeforeRender();
                }
#endif

                if (m_env.p3DEngine && !m_env.IsFMVPlaying())
                {
                    if (!IsEquivalent(m_ViewCamera.GetPosition(), Vec3(0, 0, 0), VEC_EPSILON) || // never pass undefined camera to p3DEngine->RenderWorld()
                        gEnv->IsDedicated() || (gEnv->pRenderer && gEnv->pRenderer->IsPost3DRendererEnabled()))
                    {
                        GetIRenderer()->SetViewport(0, 0, GetIRenderer()->GetWidth(), GetIRenderer()->GetHeight());
                        m_env.p3DEngine->RenderWorld(SHDF_ALLOW_WATER | SHDF_ALLOWPOSTPROCESS | SHDF_ALLOWHDR | SHDF_ZPASS | SHDF_ALLOW_AO, SRenderingPassInfo::CreateGeneralPassRenderingInfo(m_ViewCamera), __FUNCTION__);
                    }
                    else
                    {
                        if (gEnv->pRenderer)
                        {
                            // force rendering of black screen to be sure we don't only render the clear color (which is the fog color by default)
                            gEnv->pRenderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST);
                            gEnv->pRenderer->Draw2dImage(0, 0, 800, 600, -1, 0.0f, 0.0f, 1.0f, 1.0f,   0.f,
                                0.0f, 0.0f, 0.0f, 1.0f, 0.f);
                        }
                    }
                }
#if !defined(_RELEASE)
                if (m_pVisRegTest)
                {
                    m_pVisRegTest->AfterRender();
                }
                if (m_pTestSystem)
                {
                    m_pTestSystem->AfterRender();
                }

                //          m_pProcess->Draw();

                if (m_env.pEntitySystem)
                {
                    m_env.pEntitySystem->DebugDraw();
                }
#endif

                if (m_env.pMovieSystem)
                {
                    m_env.pMovieSystem->Render();
                }
            }
        }
        else
        {
            GetIRenderer()->SetViewport(0, 0, GetIRenderer()->GetWidth(), GetIRenderer()->GetHeight());
            m_pProcess->RenderWorld(SHDF_ALLOW_WATER | SHDF_ALLOWPOSTPROCESS | SHDF_ALLOWHDR | SHDF_ZPASS | SHDF_ALLOW_AO, SRenderingPassInfo::CreateGeneralPassRenderingInfo(m_ViewCamera), __FUNCTION__);
        }
    }

    m_env.p3DEngine->WorldStreamUpdate();

    gEnv->pRenderer->SwitchToNativeResolutionBackbuffer();
}

//////////////////////////////////////////////////////////////////////////
void CSystem::RenderMemStats()
{
#if !defined(_RELEASE)
    // check for the presence of the system
    if (!m_env.pConsole || !m_env.pRenderer || !m_cvMemStats->GetIVal())
    {
        SAFE_DELETE(m_pMemStats);
        return;
    }

    TickMemStats();

    assert (m_pMemStats);
    m_pMemStats->updateKeys();
    // render the statistics
    {
        CrySizerStatsRenderer StatsRenderer (this, m_pMemStats, m_cvMemStatsMaxDepth->GetIVal(), m_cvMemStatsThreshold->GetIVal());
        StatsRenderer.render((m_env.pRenderer->GetFrameID(false) + 2) % m_cvMemStats->GetIVal() <= 1);
    }
#endif
}

//////////////////////////////////////////////////////////////////////////
void CSystem::RenderStats()
{
#if defined(ENABLE_PROFILING_CODE)
#ifndef _RELEASE
    // if we rendered an error message on screen during the last frame, then sleep now
    // to force hard stall for 3sec
    if (m_bHasRenderedErrorMessage && !gEnv->IsEditor() && !IsLoading())
    {
        // DO NOT REMOVE OR COMMENT THIS OUT!
        // If you hit this, then you most likely have invalid (synchronous) file accesses
        // which must be fixed in order to not stall the entire game.
        Sleep(3000);
        m_bHasRenderedErrorMessage = false;
    }
#endif

    // render info messages on screen
    float fTextPosX = 5.0f;
    float fTextPosY = -10;
    float fTextStepY = 13;

    float fFrameTime = gEnv->pTimer->GetRealFrameTime();
    TErrorMessages::iterator itnext;
    for (TErrorMessages::iterator it = m_ErrorMessages.begin(); it != m_ErrorMessages.end(); it = itnext)
    {
        itnext = it;
        ++itnext;
        SErrorMessage& message = *it;

        SDrawTextInfo ti;
        ti.flags = eDrawText_FixedSize | eDrawText_2D | eDrawText_Monospace;
        memcpy(ti.color, message.m_Color, 4 * sizeof(float));
        ti.xscale = ti.yscale = 1.4f;
        m_env.pRenderer->DrawTextQueued(Vec3(fTextPosX, fTextPosY += fTextStepY, 1.0f), ti, message.m_Message.c_str());

        if (!IsLoading())
        {
            message.m_fTimeToShow -= fFrameTime;
        }

        if (message.m_HardFailure)
        {
            m_bHasRenderedErrorMessage = true;
        }

        if (message.m_fTimeToShow < 0.0f)
        {
            m_ErrorMessages.erase(it);
        }
    }
#endif

    if (!m_env.pConsole)
    {
        return;
    }

#ifndef _RELEASE
    if (m_rOverscanBordersDrawDebugView)
    {
        RenderOverscanBorders();
    }
#endif

    int iDisplayInfo = m_rDisplayInfo->GetIVal();
    if (iDisplayInfo == 0)
    {
        return;
    }

    // Draw engine stats
    if (m_env.p3DEngine)
    {
        // Draw 3dengine stats and get last text cursor position
        float nTextPosX = 101 - 20, nTextPosY = -2, nTextStepY = 3;
        m_env.p3DEngine->DisplayInfo(nTextPosX, nTextPosY, nTextStepY, iDisplayInfo != 1);

        // Dump Lumberyard CPU and GPU memory statistics to screen
        m_env.p3DEngine->DisplayMemoryStatistics();

    #if defined(ENABLE_LW_PROFILERS)
        if (m_rDisplayInfo->GetIVal() == 2)
        {
            m_env.pRenderer->TextToScreen(nTextPosX, nTextPosY += nTextStepY, "SysMem %.1f mb",
                float(DumpMMStats(false)) / 1024.f);
        }
    #endif

    #if 0
        for (int i = 0; i < NUM_POOLS; ++i)
        {
            int used = (g_pPakHeap->m_iBigPoolUsed[i] ? (int)g_pPakHeap->m_iBigPoolSize[i] : 0);
            int size = (int)g_pPakHeap->m_iBigPoolSize[i];
            float fC1[4] = {1, 1, 0, 1};
            m_env.pRenderer->Draw2dLabel(10, 100.0f + i * 16, 2.1f, fC1, false,  "BigPool %d: %d bytes of %d bytes used", i, used, size);
        }
    #endif
    }
}

void CSystem::RenderOverscanBorders()
{
#ifndef _RELEASE

    if (m_env.pRenderer && m_rOverscanBordersDrawDebugView)
    {
        int iOverscanBordersDrawDebugView = m_rOverscanBordersDrawDebugView->GetIVal();
        if (iOverscanBordersDrawDebugView)
        {
            const int texId = -1;
            const float uv = 0.0f;
            const float rot = 0.0f;
            const int whiteTextureId = m_env.pRenderer->GetWhiteTextureId();

            const float r = 1.0f;
            const float g = 1.0f;
            const float b = 1.0f;
            const float a = 0.2f;

            Vec2 overscanBorders = Vec2(0.0f, 0.0f);
            m_env.pRenderer->EF_Query(EFQ_OverscanBorders, overscanBorders);

            const float overscanBorderWidth = overscanBorders.x * VIRTUAL_SCREEN_WIDTH;
            const float overscanBorderHeight = overscanBorders.y * VIRTUAL_SCREEN_HEIGHT;

            const float xPos = overscanBorderWidth;
            const float yPos = overscanBorderHeight;
            const float width = VIRTUAL_SCREEN_WIDTH - (2.0f * overscanBorderWidth);
            const float height = VIRTUAL_SCREEN_HEIGHT - (2.0f * overscanBorderHeight);

            m_env.pRenderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST);
            m_env.pRenderer->Draw2dImage(xPos, yPos,
                width, height,
                whiteTextureId,
                uv, uv, uv, uv,
                rot,
                r, g, b, a);
        }
    }
#endif
}

void CSystem::RenderThreadInfo()
{
#if !defined(RELEASE) && AZ_LEGACY_CRYSYSTEM_TRAIT_RENDERTHREADINFO
    if (g_cvars.sys_display_threads)
    {
        static int maxCPU = 0;
        const int maxAvailCPU = 6;
        static unsigned int maxMask = 0;

        if (maxCPU == 0)
        {
            SYSTEM_INFO sysinfo;
            GetSystemInfo(&sysinfo);
            maxCPU = sysinfo.dwNumberOfProcessors;
            maxMask = (1 << maxCPU) - 1;
        }

        struct SThreadProcessorInfo
        {
            SThreadProcessorInfo(const string& _name, uint32 _id)
                : name(_name)
                , id(_id) {}
            string name;
            uint32 id;
        };

        static std::multimap<DWORD, SThreadProcessorInfo> sortetThreads;
        static SThreadInfo::TThreads threads;
        static SThreadInfo::TThreadInfo threadInfo;
        static int frame = 0;

        if ((frame++ % 100) == 0) // update thread info every 100 frame
        {
            threads.clear();
            threadInfo.clear();
            sortetThreads.clear();
            SThreadInfo::OpenThreadHandles(threads, SThreadInfo::TThreadIds(), false);
            SThreadInfo::GetCurrentThreads(threadInfo);
            for (SThreadInfo::TThreads::const_iterator it = threads.begin(); it != threads.end(); ++it)
            {
                DWORD mask = (DWORD)SetThreadAffinityMask(it->Handle, maxMask);
                SetThreadAffinityMask(it->Handle, mask);
                sortetThreads.insert(std::make_pair(mask, SThreadProcessorInfo(threadInfo[it->Id], it->Id)));
            }
            SThreadInfo::CloseThreadHandles(threads);
        }

        float nX = 5, nY = 10, dY = 12, dX = 10;
        float fFSize = 1.2f;
        ColorF col1 = Col_Yellow;
        ColorF col2 = Col_Red;

        for (int i = 0; i < maxCPU; ++i)
        {
            gEnv->pRenderer->Draw2dLabel(nX + i * dX, nY, fFSize, i < maxAvailCPU ? &col1.r : &col2.r, false, "%i", i + 1);
        }

        nY += dY;
        for (std::multimap<DWORD, SThreadProcessorInfo>::const_iterator it = sortetThreads.begin(); it != sortetThreads.end(); ++it)
        {
            for (int i = 0; i < maxCPU; ++i)
            {
                gEnv->pRenderer->Draw2dLabel(nX + i * dX, nY, fFSize, i < maxAvailCPU ? &col1.r : &col2.r, false, "%s", it->first & BIT(i) ? "X" : "-");
            }

            gEnv->pRenderer->Draw2dLabel(nX + dX * maxCPU, nY, fFSize, &col1.r, false, "Thread: %s (0x%X)", it->second.name.c_str(), it->second.id);
            nY += dY;
        }
    }
#endif
}