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/RenderDll/XRenderD3D9/PipelineProfiler.cpp

775 lines
27 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.
#include "RenderDll_precompiled.h"
#include "PipelineProfiler.h"
#include "DriverD3D.h"
#include "../Common/Textures/TextureManager.h"
CRenderPipelineProfiler::CRenderPipelineProfiler()
{
m_sectionsFrameIdx = 0;
m_gpuSyncTime = 0;
m_avgFrameTime = 0;
m_enabled = false;
m_recordData = false;
m_waitForGPUTimers = false;
m_stack.reserve(8);
for (int i = 0; i < RT_COMMAND_BUF_COUNT; i++)
{
ResetBasicStats(m_basicStats[i], true);
}
}
void CRenderPipelineProfiler::BeginFrame()
{
AZ_TRACE_METHOD();
m_recordData = IsEnabled();
if (gEnv->IsEditor() && !gcpRendD3D->m_CurrContext->m_bMainViewport)
{
m_recordData = false;
}
if (m_recordData)
{
CD3DProfilingGPUTimer::EnableTiming();
}
uint32 nextSectionsFrameIdx = (m_sectionsFrameIdx + 1) % NumSectionsFrames;
RPPSectionsFrame& nextSectionsFrame = m_sectionsFrames[nextSectionsFrameIdx];
if (nextSectionsFrame.m_numSections > 0)
{
RPProfilerSection& lastSection = nextSectionsFrame.m_sections[nextSectionsFrame.m_numSections - 1];
lastSection.gpuTimer.UpdateTime();
if (lastSection.gpuTimer.HasPendingQueries())
{
// Don't record new data if there are still pending GPU time queries, otherwise results may be wrong when labels change between frames
m_recordData = false;
return;
}
}
m_sectionsFrameIdx = nextSectionsFrameIdx;
if (!WaitForTimers())
{
UpdateBasicStats();
}
nextSectionsFrame.m_numSections = 0;
BeginSection("FRAME", 0);
if (m_recordData)
{
nextSectionsFrame.m_sections[0].numDIPs = 0;
nextSectionsFrame.m_sections[0].numPolys = 0;
}
}
void CRenderPipelineProfiler::EndFrame()
{
EndSection("FRAME");
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[m_sectionsFrameIdx];
if (!m_stack.empty())
{
sectionsFrame.m_sections[0].recLevel = -1;
m_stack.resize(0);
}
m_gpuSyncTime = 0;
if (sectionsFrame.m_numSections > 0 && WaitForTimers())
{
CTimeValue startTime = gEnv->pTimer->GetAsyncTime();
RPProfilerSection& lastSection = sectionsFrame.m_sections[sectionsFrame.m_numSections - 1];
// This will lower the overall framerate but gives accurate GPU times
do
{
lastSection.gpuTimer.UpdateTime();
} while (lastSection.gpuTimer.HasPendingQueries());
m_gpuSyncTime = gEnv->pTimer->GetAsyncTime().GetDifferenceInSeconds(startTime) * 1000.0f;
UpdateBasicStats();
}
UpdateThreadTimings();
m_recordData = false;
// Don't display stats on anything but the main editor viewport
// Also don't display stats if the renderer hasn't finished loading some default resources
// We can't display stats properly without loading the default White texture
if ((!gEnv->IsEditor() || (gEnv->IsEditor() && gcpRendD3D->m_CurrContext->m_bMainViewport)) && gEnv->pRenderer->HasLoadedDefaultResources())
{
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_profiler == 1)
{
DisplayBasicStats();
}
else if IsCVarConstAccess(constexpr) (CRenderer::CV_r_profiler == 2)
{
DisplayAdvancedStats();
}
}
}
void CRenderPipelineProfiler::ReleaseGPUTimers()
{
for (uint32 i = 0; i < NumSectionsFrames; ++i)
{
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[i];
for (uint32 j = 0; j < sectionsFrame.m_numSections; ++j)
{
RPProfilerSection& section = sectionsFrame.m_sections[j];
section.gpuTimer.Stop();
section.gpuTimer.Release();
}
}
}
bool CRenderPipelineProfiler::FilterLabel(const char* name)
{
return
(strcmp(name, "SCREEN_STRETCH_RECT") == 0) ||
(strcmp(name, "STRETCHRECT_EMU") == 0) ||
(strcmp(name, "STENCIL_VOLUME") == 0) ||
(strcmp(name, "DRAWSTRINGW") == 0); // FIXME: Label stack error in DRAWSTRINGW
}
void CRenderPipelineProfiler::BeginSection(const char* name, [[maybe_unused]] uint32 eProfileLabelFlags)
{
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[m_sectionsFrameIdx];
if (sectionsFrame.m_numSections >= RPPSectionsFrame::MaxNumSections)
{
m_recordData = false;
}
if (!m_recordData || FilterLabel(name))
{
return;
}
RPProfilerSection& section = sectionsFrame.m_sections[sectionsFrame.m_numSections++];
cry_strcpy(section.name, name);
section.recLevel = m_stack.size() + 1;
section.numDIPs = gcpRendD3D->GetCurrentNumberOfDrawCalls();
section.numPolys = gcpRendD3D->GetPolyCount();
section.startTimeCPU = gEnv->pTimer->GetAsyncTime();
section.gpuTimer.Start(name);
m_stack.push_back(sectionsFrame.m_numSections - 1);
string path = "";
for (int i = 0; i < m_stack.size(); i++)
{
path += "\\";
path += sectionsFrame.m_sections[m_stack[i]].name;
}
section.path = CCryNameTSCRC(path);
}
void CRenderPipelineProfiler::EndSection(const char* name)
{
if (!m_recordData || FilterLabel(name))
{
return;
}
if (!m_stack.empty())
{
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[m_sectionsFrameIdx];
RPProfilerSection& section = sectionsFrame.m_sections[m_stack.back()];
section.numDIPs = gcpRendD3D->GetCurrentNumberOfDrawCalls() - section.numDIPs;
section.numPolys = gcpRendD3D->GetPolyCount() - section.numPolys;
section.endTimeCPU = gEnv->pTimer->GetAsyncTime();
section.gpuTimer.Stop();
if (strncmp(section.name, name, 30) != 0)
{
section.recLevel = -section.recLevel;
}
m_stack.pop_back();
}
}
void CRenderPipelineProfiler::UpdateThreadTimings()
{
const float weight = 8.0f / 9.0f;
const uint32 fillThreadID = (uint32)gcpRendD3D->m_RP.m_nFillThreadID;
m_threadTimings.waitForMain = (gcpRendD3D->m_fTimeWaitForMain[fillThreadID] * (1.0f - weight) + m_threadTimings.waitForMain * weight);
m_threadTimings.waitForRender = (gcpRendD3D->m_fTimeWaitForRender[fillThreadID] * (1.0f - weight) + m_threadTimings.waitForRender * weight);
m_threadTimings.waitForGPU = (gcpRendD3D->m_fTimeWaitForGPU[fillThreadID] * (1.0f - weight) + m_threadTimings.waitForGPU * weight);
m_threadTimings.gpuIdlePerc = (gcpRendD3D->m_fTimeGPUIdlePercent[fillThreadID] * (1.0f - weight) + m_threadTimings.gpuIdlePerc * weight);
m_threadTimings.gpuFrameTime = (gcpRendD3D->m_fTimeProcessedGPU[fillThreadID] * (1.0f - weight) + m_threadTimings.gpuFrameTime * weight);
m_threadTimings.frameTime = (iTimer->GetRealFrameTime() * (1.0f - weight) + m_threadTimings.frameTime * weight);
m_threadTimings.renderTime = min((gcpRendD3D->m_fTimeProcessedRT[fillThreadID] * (1.0f - weight) + m_threadTimings.renderTime * weight), m_threadTimings.frameTime);
}
ILINE void AddToStats(RPProfilerStats& outStats, RPProfilerSection& section)
{
outStats.gpuTime += section.gpuTimer.GetTime();
outStats.cpuTime += section.endTimeCPU.GetDifferenceInSeconds(section.startTimeCPU) * 1000.0f;
outStats.numDIPs += section.numDIPs;
outStats.numPolys += section.numPolys;
}
ILINE void SubtractFromStats(RPProfilerStats& outStats, RPProfilerSection& section)
{
outStats.gpuTime -= section.gpuTimer.GetTime();
outStats.cpuTime -= section.endTimeCPU.GetDifferenceInSeconds(section.startTimeCPU) * 1000.0f;
outStats.numDIPs -= section.numDIPs;
outStats.numPolys -= section.numPolys;
}
void CRenderPipelineProfiler::ResetBasicStats(RPProfilerStats* pBasicStats, bool bResetAveragedStats)
{
for (uint32 i = 0; i < RPPSTATS_NUM; ++i)
{
RPProfilerStats& basicStat = pBasicStats[i];
basicStat.gpuTime = 0.0f;
basicStat.cpuTime = 0.0f;
basicStat.numDIPs = 0;
basicStat.numPolys = 0;
}
if (bResetAveragedStats)
{
for (uint32 i = 0; i < RPPSTATS_NUM; ++i)
{
RPProfilerStats& basicStat = pBasicStats[i];
basicStat.gpuTimeSmoothed = 0.0f;
basicStat.gpuTimeMax = 0.0f;
basicStat._gpuTimeMaxNew = 0.0f;
}
}
}
void CRenderPipelineProfiler::ComputeAverageStats()
{
static int s_frameCounter = 0;
const int kUpdateFrequency = 60;
const uint32 processThreadID = (uint32)gcpRendD3D->m_RP.m_nProcessThreadID;
const uint32 fillThreadID = (uint32)gcpRendD3D->m_RP.m_nFillThreadID;
// GPU times
for (uint32 i = 0; i < RPPSTATS_NUM; ++i)
{
RPProfilerStats& basicStat = m_basicStats[processThreadID][i];
basicStat.gpuTimeSmoothed = 0.9f * m_basicStats[fillThreadID][i].gpuTimeSmoothed + 0.1f * basicStat.gpuTime;
float gpuTimeMax = std::max(basicStat._gpuTimeMaxNew, m_basicStats[fillThreadID][i]._gpuTimeMaxNew);
basicStat._gpuTimeMaxNew = std::max(gpuTimeMax, basicStat.gpuTime);
if (s_frameCounter % kUpdateFrequency == 0)
{
basicStat.gpuTimeMax = basicStat._gpuTimeMaxNew;
basicStat._gpuTimeMaxNew = 0;
}
}
s_frameCounter += 1;
}
void CRenderPipelineProfiler::UpdateBasicStats()
{
RPProfilerStats* pBasicStats = m_basicStats[gcpRendD3D->m_RP.m_nProcessThreadID];
ResetBasicStats(pBasicStats, false);
bool bRecursivePass = false;
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[m_sectionsFrameIdx];
for (uint32 i = 0; i < sectionsFrame.m_numSections; ++i)
{
RPProfilerSection& section = sectionsFrame.m_sections[i];
section.gpuTimer.UpdateTime();
if (strcmp(section.name, "SCENE_REC") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_Recursion], section);
bRecursivePass = true;
}
else if (strcmp(section.name, "SCENE") == 0)
{
bRecursivePass = false;
}
if (bRecursivePass)
{
continue;
}
// Scene
if (strcmp(section.name, "GBUFFER") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_SceneOverall], section);
}
else if (strcmp(section.name, "DECALS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_SceneDecals], section);
}
else if (strcmp(section.name, "DEFERRED_DECALS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_SceneOverall], section);
AddToStats(pBasicStats[eRPPSTATS_SceneDecals], section);
}
else if (strcmp(section.name, "OPAQUE_PASSES") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_SceneOverall], section);
AddToStats(pBasicStats[eRPPSTATS_SceneForward], section);
}
else if (strcmp(section.name, "WATER") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_SceneOverall], section);
AddToStats(pBasicStats[eRPPSTATS_SceneWater], section);
}
// Shadows
else if (strcmp(section.name, "SHADOWMAPS SUN") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_ShadowsOverall], section);
AddToStats(pBasicStats[eRPPSTATS_ShadowsSun], section);
}
else if (strcmp(section.name, "CUSTOM MAPS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_ShadowsSunCustom], section);
}
else if (strcmp(section.name, "SHADOWMAP_POOL") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_ShadowsOverall], section);
AddToStats(pBasicStats[eRPPSTATS_ShadowsLocal], section);
}
// Lighting
else if (strcmp(section.name, "TILED_SHADING") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
}
else if (strcmp(section.name, "DEFERRED_SHADING") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
}
else if (strcmp(section.name, "DEFERRED_CUBEMAPS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
}
else if (strcmp(section.name, "DEFERRED_LIGHTS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
}
else if (strcmp(section.name, "AMBIENT_PASS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
}
else if (strcmp(section.name, "SVOGI") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_LightingOverall], section);
AddToStats(pBasicStats[eRPPSTATS_LightingGI], section);
}
// VFX
else if (strcmp(section.name, "TRANSPARENT_BW") == 0 || strcmp(section.name, "TRANSPARENT_AW") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
AddToStats(pBasicStats[eRPPSTATS_VfxTransparent], section);
}
else if (strcmp(section.name, "FOG_GLOBAL") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
AddToStats(pBasicStats[eRPPSTATS_VfxFog], section);
}
else if (strcmp(section.name, "VOLUMETRIC FOG") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
AddToStats(pBasicStats[eRPPSTATS_VfxFog], section);
}
else if (strcmp(section.name, "DEFERRED_RAIN") == 0 || strcmp(section.name, "RAIN") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
}
else if (strcmp(section.name, "LENS_OPTICS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
AddToStats(pBasicStats[eRPPSTATS_VfxFlares], section);
}
else if (strcmp(section.name, "OCEAN CAUSTICS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
}
else if (strcmp(section.name, "WATERVOLUME_CAUSTICS") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_VfxOverall], section);
}
// Extra TI states
else if (strcmp(section.name, "TI_INJECT_CLEAR") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_CLEAR], section);
}
else if (strcmp(section.name, "TI_VOXELIZE") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_VOXELIZE], section);
}
else if (strcmp(section.name, "TI_INJECT_LIGHT") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_LIGHT], section);
}
else if (strcmp(section.name, "TI_INJECT_AIR") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_AIR], section);
}
else if (strcmp(section.name, "TI_INJECT_REFL0") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_REFL0], section);
}
else if (strcmp(section.name, "TI_INJECT_REFL1") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_REFL1], section);
}
else if (strcmp(section.name, "TI_INJECT_DYNL") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_INJECT_DYNL], section);
}
else if (strcmp(section.name, "TI_NID_DIFF") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_NID_DIFF], section);
}
else if (strcmp(section.name, "TI_GEN_DIFF") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_GEN_DIFF], section);
}
else if (strcmp(section.name, "TI_GEN_SPEC") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_GEN_SPEC], section);
}
else if (strcmp(section.name, "TI_GEN_AIR") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_GEN_AIR], section);
}
else if (strcmp(section.name, "TI_UPSCALE_DIFF") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_UPSCALE_DIFF], section);
}
else if (strcmp(section.name, "TI_UPSCALE_SPEC") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_UPSCALE_SPEC], section);
}
else if (strcmp(section.name, "TI_DEMOSAIC_DIFF") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_DEMOSAIC_DIFF], section);
}
else if (strcmp(section.name, "TI_DEMOSAIC_SPEC") == 0)
{
AddToStats(pBasicStats[eRPPSTATS_TI_DEMOSAIC_SPEC], section);
}
}
AddToStats(pBasicStats[eRPPSTATS_OverallFrame], sectionsFrame.m_sections[0]);
ComputeAverageStats();
}
void CRenderPipelineProfiler::DisplayAdvancedStats()
{
#ifndef _RELEASE
CD3D9Renderer* rd = gcpRendD3D;
RPPSectionsFrame& sectionsFrame = m_sectionsFrames[m_sectionsFrameIdx];
uint32 elemsPerColumn = (gcpRendD3D->GetHeight() - 60) / 16;
ColorF color = sectionsFrame.m_numSections >= RPPSectionsFrame::MaxNumSections ? Col_Red : ColorF(1.0f, 1.0f, 0.2f, 1);
m_avgFrameTime = 0.8f * gEnv->pTimer->GetRealFrameTime() + 0.2f * m_avgFrameTime; // exponential moving average for frame time
rd->Draw2dLabel(20, 10, 1.7f, &color.r, false, "FPS %.1f GPU Sync %.1fms", 1.0f / m_avgFrameTime, m_gpuSyncTime);
color.r = color.g = color.b = 0.35f;
rd->Draw2dLabel(320, 10, 1.5f, &color.r, false, "GPU");
rd->Draw2dLabel(400, 10, 1.5f, &color.r, false, "CPU");
rd->Draw2dLabel(470, 10, 1.5f, &color.r, false, "DIPs");
rd->Draw2dLabel(520, 10, 1.5f, &color.r, false, "Polys");
if (sectionsFrame.m_numSections > elemsPerColumn)
{
rd->Draw2dLabel(320 + 600, 10, 1.5f, &color.r, false, "GPU");
rd->Draw2dLabel(400 + 600, 10, 1.5f, &color.r, false, "CPU");
rd->Draw2dLabel(470 + 600, 10, 1.5f, &color.r, false, "DIPs");
rd->Draw2dLabel(520 + 600, 10, 1.5f, &color.r, false, "Polys");
}
// refresh the list every 3 seconds to clear out old data and reduce gaps
CTimeValue currentTime = gEnv->pTimer->GetAsyncTime();
static CTimeValue lastClearTime = currentTime;
if (currentTime.GetDifferenceInSeconds(lastClearTime) > 3.0f)
{
lastClearTime = currentTime;
m_staticNameList.clear();
}
// reset the used flag
for (std::multimap<CCryNameTSCRC, SStaticElementInfo>::iterator it = m_staticNameList.begin(); it != m_staticNameList.end(); ++it)
{
it->second.bUsed = false;
}
static float gpuTimeAverage[2] = { 10, 10 };
gpuTimeAverage[1] = gpuTimeAverage[0];
for (uint32 i = 0; i < sectionsFrame.m_numSections; ++i)
{
RPProfilerSection& section = sectionsFrame.m_sections[i];
// find the slot we should render to according to the static list - if not found, insert
std::multimap<CCryNameTSCRC, SStaticElementInfo>::iterator it = m_staticNameList.lower_bound(section.path);
while (it != m_staticNameList.end() && it->second.bUsed)
{
++it;
}
// not found: either a new element or a duplicate - insert at correct position and render there
if (it == m_staticNameList.end() || it->first != section.path)
{
it = m_staticNameList.insert(std::make_pair(section.path, i));
}
it->second.bUsed = true;
float gpuTime = section.gpuTimer.GetTime();
float cpuTime = section.endTimeCPU.GetDifferenceInSeconds(section.startTimeCPU);
float ypos = 30.0f + (it->second.nPos % elemsPerColumn) * 16.0f;
float xpos = 20.0f + ((int)(it->second.nPos / elemsPerColumn)) * 600.0f;
color.r = color.g = color.b = min(0.4f + gpuTime * 0.4f, 0.9f);
if (section.recLevel < 0) // Label stack error
{
color.r = 1;
color.g = color.b = 0;
}
else if (i == 0) // Special case for FRAME section
{
color.r = 1;
color.g = 1;
color.b = 0.2f;
}
else if (max(gpuTime, cpuTime) > gpuTimeAverage[1] * 0.75f)
{
color.r = color.g = color.b = 1; // highlight heavy elements
}
rd->Draw2dLabel(xpos + max((int)(abs(section.recLevel) - 2), 0) * 15.0f, ypos, 1.5f, &color.r, false, section.name);
rd->Draw2dLabel(xpos + 300, ypos, 1.5f, &color.r, false, "%.2fms", gpuTime);
rd->Draw2dLabel(xpos + 380, ypos, 1.5f, &color.r, false, "%.2fms", cpuTime * 1000.0f);
rd->Draw2dLabel(xpos + 450, ypos, 1.5f, &color.r, false, "%i", section.numDIPs);
rd->Draw2dLabel(xpos + 500, ypos, 1.5f, &color.r, false, "%i", section.numPolys);
if (i != 0)
{
gpuTimeAverage[0] += (max(gpuTime, cpuTime) - gpuTimeAverage[0]) * 0.05f;
}
}
rd->RT_RenderTextMessages();
#endif
}
namespace DebugUI
{
const float columnHeight = 0.027f;
void DrawText(float x, float y, float size, ColorF color, const char* text)
{
CD3D9Renderer* rd = gcpRendD3D;
float aspect = (float)rd->GetOverlayWidth() / (float)rd->GetOverlayHeight();
float sx = VIRTUAL_SCREEN_WIDTH / aspect;
float sy = VIRTUAL_SCREEN_HEIGHT;
SDrawTextInfo ti;
ti.xscale = size * 1.55f / aspect;
ti.yscale = size * 1.1f;
ti.color[0] = color.r;
ti.color[1] = color.r;
ti.color[2] = color.b;
ti.color[3] = color.a;
ti.flags = eDrawText_800x600 | eDrawText_2D;
rd->Draw2dText(x * sx, y * sy, text, ti);
}
void DrawText(float x, float y, float size, ColorF color, const char* format, va_list args)
{
char buffer[512];
if (azvsnprintf(buffer, sizeof(buffer), format, args) == -1)
{
buffer[sizeof(buffer) - 1] = 0;
}
DrawText(x, y, size, color, buffer);
}
void DrawBox(float x, float y, float width, float height, ColorF color)
{
//s_ptexWhite is not supposed to be nullptr here
//This can happen if the profiler is asked to draw before the level loads
AZ_Assert(CTextureManager::Instance()->GetWhiteTexture() != nullptr, "Pipeline Profiler tried to draw a box but White textue was nullptr! Have the default texture resources been loaded yet?");
CD3D9Renderer* rd = gcpRendD3D;
float aspect = (float)rd->GetOverlayWidth() / (float)rd->GetOverlayHeight();
float sx = VIRTUAL_SCREEN_WIDTH / aspect;
float sy = VIRTUAL_SCREEN_HEIGHT;
const Vec2 overscanOffset = Vec2(rd->s_overscanBorders.x * VIRTUAL_SCREEN_WIDTH, rd->s_overscanBorders.y * VIRTUAL_SCREEN_HEIGHT);
rd->Draw2dImage(x * sx + overscanOffset.x, y * sy + overscanOffset.y, width * sx, height * sy, CTextureManager::Instance()->GetWhiteTexture()->GetID(), 0, 0, 1, 1, 0, color);
}
void DrawTable(float x, float y, float width, int numColumns, const char* title)
{
DrawBox(x, y, width, columnHeight, ColorF(0.45f, 0.45f, 0.55f, 0.6f));
DrawBox(x, y + columnHeight, width, columnHeight * (float)numColumns + 0.007f, ColorF(0.05f, 0.05f, 0.05f, 0.6f));
DrawText(x + 0.006f, y + 0.004f, 1, Col_Salmon, title);
}
void DrawTableColumn(float tableX, float tableY, int columnIndex, const char* columnText, ...)
{
va_list args;
va_start(args, columnText);
DrawText(tableX + 0.02f, tableY + (float)(columnIndex + 1) * columnHeight + 0.005f, 1, Col_White, columnText, args);
va_end(args);
}
void DrawTableBar(float x, float tableY, int columnIndex, float percentage, ColorF color)
{
const float barHeight = 0.02f;
const float barWidth = 0.15f;
DrawBox(x, tableY + (float)(columnIndex + 1) * columnHeight + (columnHeight - barHeight) * 0.5f + 0.005f,
barWidth, barHeight, ColorF(1, 1, 1, 0.2f));
DrawBox(x, tableY + (float)(columnIndex + 1) * columnHeight + (columnHeight - barHeight) * 0.5f + 0.005f,
percentage * barWidth, barHeight, ColorF(color.r, color.g, color.b, 0.7f));
}
}
void CRenderPipelineProfiler::DisplayBasicStats()
{
#ifndef _RELEASE
if (gEnv->pConsole->IsOpened())
{
return;
}
CD3D9Renderer* rd = gcpRendD3D;
struct StatsGroup
{
char name[32];
ERenderPipelineProfilerStats statIndex;
};
StatsGroup statsGroups[] = {
{ "Frame", eRPPSTATS_OverallFrame },
{ " Ocean Reflections", eRPPSTATS_Recursion },
{ " Scene", eRPPSTATS_SceneOverall },
{ " Decals", eRPPSTATS_SceneDecals },
{ " Forward", eRPPSTATS_SceneForward },
{ " Water", eRPPSTATS_SceneWater },
{ " Shadows", eRPPSTATS_ShadowsOverall },
{ " Sun", eRPPSTATS_ShadowsSun },
{ " Per-Object", eRPPSTATS_ShadowsSunCustom },
{ " Local", eRPPSTATS_ShadowsLocal },
{ " Lighting", eRPPSTATS_LightingOverall },
{ " Voxel GI", eRPPSTATS_LightingGI },
{ " VFX", eRPPSTATS_VfxOverall },
{ " Particles/Glass", eRPPSTATS_VfxTransparent },
{ " Fog", eRPPSTATS_VfxFog },
{ " Flares", eRPPSTATS_VfxFlares },
};
rd->SetState(GS_NODEPTHTEST | GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA);
// Threading info
{
DebugUI::DrawTable(0.05f, 0.1f, 0.45f, 4, "Overview");
float frameTime = m_threadTimings.frameTime;
float mainThreadTime = max(m_threadTimings.frameTime - m_threadTimings.waitForRender, 0.0f);
float renderThreadTime = max(m_threadTimings.renderTime - m_threadTimings.waitForGPU, 0.0f);
#if defined(AZ_RESTRICTED_PLATFORM)
#include AZ_RESTRICTED_FILE(PipelineProfiler_cpp)
#endif
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
float gpuTime = max(m_threadTimings.gpuFrameTime, 0.0f);
#endif
float waitForGPU = max(m_threadTimings.waitForGPU, 0.0f);
DebugUI::DrawTableBar(0.335f, 0.1f, 0, mainThreadTime / frameTime, Col_Yellow);
DebugUI::DrawTableBar(0.335f, 0.1f, 1, renderThreadTime / frameTime, Col_Green);
DebugUI::DrawTableBar(0.335f, 0.1f, 2, gpuTime / frameTime, Col_Cyan);
DebugUI::DrawTableBar(0.335f, 0.1f, 3, waitForGPU / frameTime, Col_Red);
DebugUI::DrawTableColumn(0.05f, 0.1f, 0, "Main Thread %6.2f ms", mainThreadTime * 1000.0f);
DebugUI::DrawTableColumn(0.05f, 0.1f, 1, "Render Thread %6.2f ms", renderThreadTime * 1000.0f);
DebugUI::DrawTableColumn(0.05f, 0.1f, 2, "GPU %6.2f ms", gpuTime * 1000.0f);
DebugUI::DrawTableColumn(0.05f, 0.1f, 3, "CPU waits for GPU %6.2f ms", waitForGPU * 1000.0f);
}
// GPU times
{
const float targetFrameTime = 1000.0f / CRenderer::CV_r_profilerTargetFPS;
int numColumns = sizeof(statsGroups) / sizeof(StatsGroup);
DebugUI::DrawTable(0.05f, 0.27f, 0.45f, numColumns, "GPU Time");
RPProfilerStats* pBasicStats = m_basicStats[gcpRendD3D->m_RP.m_nProcessThreadID];
for (uint32 i = 0; i < numColumns; ++i)
{
const RPProfilerStats& stats = pBasicStats[statsGroups[i].statIndex];
DebugUI::DrawTableColumn(0.05f, 0.27f, i, "%-20s %4.1f ms %2.0f %%", statsGroups[i].name, stats.gpuTimeSmoothed, stats.gpuTimeSmoothed / targetFrameTime * 100.0f);
}
}
rd->RT_RenderTextMessages();
#endif
}
bool CRenderPipelineProfiler::IsEnabled()
{
return m_enabled || CRenderer::CV_r_profiler;
}