You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/SystemRender.cpp

622 lines
21 KiB
C++

/*
* 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 "CrySystem_precompiled.h"
#include "System.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#if defined(AZ_PLATFORM_IOS)
#import <UIKit/UIKit.h>
#endif
#include <IRenderer.h>
#include <IRenderAuxGeom.h>
#include <IProcess.h>
#include "Log.h"
#include "XConsole.h"
#include <I3DEngine.h>
#include <CryLibrary.h>
#include "PhysRenderer.h"
#include <IMovieSystem.h>
#include "CrySizerStats.h"
#include "CrySizerImpl.h"
#include "VisRegTest.h"
#include "ITextModeConsole.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>
#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([[maybe_unused]] int& o_widthPixels, [[maybe_unused]] 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);
#else
return false;
#endif
}
bool CSystem::IsTablet()
{
//TODO: Add support for Android tablets
#if defined(AZ_PLATFORM_IOS)
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
#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)
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
#include AZ_RESTRICTED_FILE(SystemRender_cpp)
#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;
}
//////////////////////////////////////////////////////////////////////////
void CSystem::RenderEnd([[maybe_unused]] bool bRenderStats, bool bMainWindow)
{
{
FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_SYSTEM, g_bProfilerEnabled);
if (m_bIgnoreUpdates)
{
return;
}
if (!m_env.pRenderer)
{
return;
}
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 (!gEnv->pSystem->GetILevelSystem() || !gEnv->pSystem->GetILevelSystem()->IsLevelLoaded())
{
IConsole* console = GetIConsole();
//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();
}
}
}
}
void CSystem::OnScene3DEnd()
{
//Render Console
if (m_bDrawConsole && gEnv->pConsole)
{
gEnv->pConsole->Draw();
}
}
//! Update screen and call some important tick functions during loading.
void CSystem::SynchronousLoadingTick([[maybe_unused]] const char* pFunc, [[maybe_unused]] 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
#include AZ_RESTRICTED_FILE(SystemRender_cpp)
#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,
[[maybe_unused]] 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();
}
//////////////////////////////////////////////////////////////////////
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 (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();
}
#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::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 Open 3D Engine 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
}