/* * 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 : Visual Regression Test #include "StdAfx.h" #include "VisRegTest.h" #include "ISystem.h" #include "I3DEngine.h" #include "IRenderer.h" #include "IConsole.h" #include "ITimer.h" #include "IEntitySystem.h" #include "IStreamEngine.h" #include const float CVisRegTest::MaxStreamingWait = 30.0f; CVisRegTest::CVisRegTest() : m_nextCmd(0) , m_waitFrames(0) { CryLog("Enabled visual regression tests"); } void CVisRegTest::Init(IConsoleCmdArgs* pParams) { assert(pParams); stack_string configFile("visregtest.xml"); // Reset m_cmdBuf.clear(); m_dataSamples.clear(); m_nextCmd = 0; m_cmdFreq = 0; m_waitFrames = 0; m_streamingTimeout = 0.0f; m_testName = "test"; m_quitAfterTests = false; // Parse arguments if (pParams->GetArgCount() >= 2) { m_testName = pParams->GetArg(1); } if (pParams->GetArgCount() >= 3) { configFile = pParams->GetArg(2); } if (pParams->GetArgCount() >= 4) { if (_stricmp(pParams->GetArg(3), "quit") == 0) { m_quitAfterTests = true; } } // Fill cmd buffer if (!LoadConfig(configFile.c_str())) { CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "VisRegTest: Failed to load config file '%s' from game folder", configFile.c_str()); return; } gEnv->pTimer->SetTimeScale(0); GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RANDOM_SEED, 0, 0); srand(0); gEnv->pRenderer->EnableGPUTimers2(true); } void CVisRegTest::AfterRender() { ExecCommands(); } bool CVisRegTest::LoadConfig(const char* configFile) { XmlNodeRef rootNode = GetISystem()->LoadXmlFromFile(configFile); if (!rootNode || !rootNode->isTag("VisRegTest")) { return false; } m_cmdBuf.push_back(SCmd(eCMDStart, "")); for (int i = 0; i < rootNode->getChildCount(); ++i) { XmlNodeRef node = rootNode->getChild(i); if (node->isTag("Config")) { SCmd cmd; for (int j = 0; j < node->getChildCount(); ++j) { XmlNodeRef node2 = node->getChild(j); if (node2->isTag("ConsoleCmd")) { m_cmdBuf.push_back(SCmd(eCMDConsoleCmd, node2->getAttr("cmd"))); } } } else if (node->isTag("Map")) { const char* mapName = node->getAttr("name"); int imageIndex = 0; m_cmdBuf.push_back(SCmd(eCMDLoadMap, mapName)); m_cmdBuf.push_back(SCmd(eCMDOnMapLoaded, "")); for (int j = 0; j < node->getChildCount(); ++j) { XmlNodeRef node2 = node->getChild(j); if (node2->isTag("ConsoleCmd")) { m_cmdBuf.push_back(SCmd(eCMDConsoleCmd, node2->getAttr("cmd"))); } else if (node2->isTag("Sample")) { m_cmdBuf.push_back(SCmd(eCMDGoto, node2->getAttr("location"))); m_cmdBuf.push_back(SCmd(eCMDWaitStreaming, "")); char strbuf[256]; #ifdef WIN32 sprintf_s(strbuf, "%s_%i.bmp", mapName, imageIndex++); #else sprintf_s(strbuf, "%s_%i.tga", mapName, imageIndex++); #endif m_cmdBuf.push_back(SCmd(eCMDCaptureSample, strbuf, SampleCount)); } } } } m_cmdBuf.push_back(SCmd(eCMDFinish, "")); return true; } void CVisRegTest::ExecCommands() { if (m_nextCmd >= m_cmdBuf.size()) { return; } float col[] = {0, 1, 0, 1}; gEnv->pRenderer->Draw2dLabel(10, 10, 2, col, false, "Visual Regression Test"); if (m_waitFrames > 0) { --m_waitFrames; return; } else if (m_waitFrames < 0) // Wait for streaming { SStreamEngineOpenStats stats; gEnv->pSystem->GetStreamEngine()->GetStreamingOpenStatistics(stats); if (stats.nOpenRequestCount > 0 && m_streamingTimeout > 0) { gEnv->pConsole->ExecuteString("t_FixedStep 0"); m_streamingTimeout -= gEnv->pTimer->GetRealFrameTime(); m_waitFrames = -16; } else if (++m_waitFrames == 0) { gEnv->pConsole->ExecuteString("t_FixedStep 0.033333"); m_waitFrames = 64; // Wait some more frames for tone-mapper to adapt } return; } stack_string tmp; while (m_nextCmd < m_cmdBuf.size()) { SCmd& cmd = m_cmdBuf[m_nextCmd]; if (m_cmdFreq == 0) { m_cmdFreq = cmd.freq; } switch (cmd.cmd) { case eCMDStart: break; case eCMDFinish: Finish(); break; case eCMDOnMapLoaded: gEnv->pTimer->SetTimer(ITimer::ETIMER_GAME, 0); gEnv->pTimer->SetTimer(ITimer::ETIMER_UI, 0); gEnv->pConsole->ExecuteString("t_FixedStep 0.033333"); GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RANDOM_SEED, 0, 0); srand(0); break; case eCMDConsoleCmd: gEnv->pConsole->ExecuteString(cmd.args.c_str()); break; case eCMDLoadMap: LoadMap(cmd.args.c_str()); break; case eCMDWaitStreaming: m_waitFrames = -1; m_streamingTimeout = MaxStreamingWait; break; case eCMDWaitFrames: m_waitFrames = (uint32)atoi(cmd.args.c_str()); break; case eCMDGoto: tmp = "playerGoto "; tmp.append(cmd.args.c_str()); gEnv->pConsole->ExecuteString(tmp.c_str()); m_waitFrames = 1; break; case eCMDCaptureSample: CaptureSample(cmd); break; } if (m_cmdFreq-- == 1) { ++m_nextCmd; } if (m_waitFrames != 0) { break; } } } void CVisRegTest::LoadMap(const char* mapName) { string mapCmd("map "); mapCmd.append(mapName); gEnv->pConsole->ExecuteString(mapCmd.c_str()); gEnv->pTimer->SetTimer(ITimer::ETIMER_GAME, 0); gEnv->pTimer->SetTimer(ITimer::ETIMER_UI, 0); gEnv->pConsole->ExecuteString("t_FixedStep 0"); GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_RANDOM_SEED, 0, 0); srand(0); } void CVisRegTest::CaptureSample(const SCmd& cmd) { if (m_cmdFreq == SampleCount) // First sample { m_dataSamples.push_back(SSample()); gEnv->pConsole->ExecuteString("t_FixedStep 0"); } SSample& sample = m_dataSamples.back(); // Collect stats sample.imageName = cmd.args.c_str(); sample.frameTime += gEnv->pTimer->GetRealFrameTime() * 1000.f; sample.drawCalls += gEnv->pRenderer->GetCurrentNumberOfDrawCalls(); const RPProfilerStats* pRPPStats = gEnv->pRenderer->GetRPPStatsArray(); if (pRPPStats) { sample.gpuTimes[0] += pRPPStats[eRPPSTATS_OverallFrame].gpuTime; sample.gpuTimes[1] += pRPPStats[eRPPSTATS_SceneOverall].gpuTime; sample.gpuTimes[2] += pRPPStats[eRPPSTATS_ShadowsOverall].gpuTime; sample.gpuTimes[3] += pRPPStats[eRPPSTATS_LightingOverall].gpuTime; sample.gpuTimes[4] += pRPPStats[eRPPSTATS_VfxOverall].gpuTime; } if (m_cmdFreq == 1) // Final sample { // Screenshot stack_string filename("@cache@/TestResults/VisReg/"); // the default unaliased assets folder is read-only! filename += m_testName + "/" + cmd.args.c_str(); gEnv->pRenderer->ScreenShot(filename); // Average results sample.frameTime /= (float)SampleCount; sample.drawCalls /= SampleCount; for (uint32 i = 0; i < MaxNumGPUTimes; ++i) { sample.gpuTimes[i] /= (float)SampleCount; } gEnv->pConsole->ExecuteString("t_FixedStep 0.033333"); } } void CVisRegTest::Finish() { WriteResults(); gEnv->pConsole->ExecuteString("t_FixedStep 0"); gEnv->pTimer->SetTimeScale(1); gEnv->pRenderer->EnableGPUTimers2(false); CryLog("VisRegTest: Finished tests"); if (m_quitAfterTests) { gEnv->pConsole->ExecuteString("quit"); return; } } bool CVisRegTest::WriteResults() { stack_string filename("@cache@/TestResults/VisReg/"); filename += m_testName + "/visreg_results.xml"; AZ::IO::HandleType fileHandle = fxopen(filename.c_str(), "wb"); if (fileHandle == AZ::IO::InvalidHandle) { return false; } AZ::IO::Print(fileHandle, "\n"); AZ::IO::Print(fileHandle, "\t\n"); for (int i = 0; i < (int)m_dataSamples.size(); ++i) { SSample sample = m_dataSamples[i]; AZ::IO::Print(fileHandle, "\t\t\n", i, sample.imageName); AZ::IO::Print(fileHandle, "\t\t\t\n"); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.frameTime); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.drawCalls); AZ::IO::Print(fileHandle, "\t\t\t\n"); AZ::IO::Print(fileHandle, "\t\t\t\n"); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[0]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[1]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[2]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[3]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[4]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[5]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[6]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[7]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[8]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[9]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[10]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[11]); AZ::IO::Print(fileHandle, "\t\t\t\t\n", sample.gpuTimes[12]); AZ::IO::Print(fileHandle, "\t\t\t\n"); AZ::IO::Print(fileHandle, "\t\t\n"); } AZ::IO::Print(fileHandle, "\t\n"); AZ::IO::Print(fileHandle, ""); gEnv->pFileIO->Close(fileHandle); return true; }