Index: source/graphics/TextureConverter.cpp =================================================================== --- source/graphics/TextureConverter.cpp +++ source/graphics/TextureConverter.cpp @@ -559,7 +559,7 @@ { #if CONFIG2_NVTT debug_SetThreadName("TextureConverter"); - g_Profiler2.RegisterCurrentThread("texconv"); + g_Profiler2->RegisterCurrentThread("texconv"); // Wait until the main thread wakes us up while (true) { @@ -570,7 +570,7 @@ textureConverter->m_WorkerCV.wait(wait_lock); } - g_Profiler2.RecordSyncMarker(); + g_Profiler2->RecordSyncMarker(); PROFILE2_EVENT("wakeup"); std::shared_ptr request; Index: source/main.cpp =================================================================== --- source/main.cpp +++ source/main.cpp @@ -108,8 +108,8 @@ // Request the high performance GPU on Windows by default if no system override is specified. // See: // - https://github.com/supertuxkart/stk-code/pull/4693/commits/0a99c667ef513b2ce0f5755729a6e05df8aac48a -// - https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm -// - https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/ +// - https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm +// - https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/ extern "C" { __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; @@ -233,7 +233,7 @@ } else if (hotkey == "profile2.toggle") { - g_Profiler2.Toggle(); + g_Profiler2->Toggle(); return IN_HANDLED; } break; @@ -355,10 +355,10 @@ static void Frame() { - g_Profiler2.RecordFrameStart(); + g_Profiler2->RecordFrameStart(); PROFILE2("frame"); - g_Profiler2.IncrementFrameNumber(); - PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); + g_Profiler2->IncrementFrameNumber(); + PROFILE2_ATTR("%d", g_Profiler2->GetFrameNumber()); // get elapsed time const double time = timer_Time(); @@ -450,10 +450,10 @@ static void NonVisualFrame() { - g_Profiler2.RecordFrameStart(); + g_Profiler2->RecordFrameStart(); PROFILE2("frame"); - g_Profiler2.IncrementFrameNumber(); - PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); + g_Profiler2->IncrementFrameNumber(); + PROFILE2_ATTR("%d", g_Profiler2->GetFrameNumber()); if (g_NetClient) g_NetClient->Poll(); @@ -734,12 +734,15 @@ } #endif // OS_UNIX - EarlyInit(); // must come at beginning of main + EarliestInit(); - RunGameOrAtlas(argc, const_cast(argv)); + // initialise profiler early so it can profile startup, + // but after timer_Init() + CProfiler2 profiler; + + EarlyInit(); - // Shut down profiler initialised by EarlyInit - g_Profiler2.Shutdown(); + RunGameOrAtlas(argc, const_cast(argv)); return EXIT_SUCCESS; } Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -416,7 +416,7 @@ void CNetServerWorker::Run() { // The script context uses the profiler and therefore the thread must be registered before the context is created - g_Profiler2.RegisterCurrentThread("Net server"); + g_Profiler2->RegisterCurrentThread("Net server"); // We create a new ScriptContext for this network thread, with a single ScriptInterface. std::shared_ptr netServerContext = ScriptContext::CreateContext(); Index: source/ps/GameSetup/GameSetup.h =================================================================== --- source/ps/GameSetup/GameSetup.h +++ source/ps/GameSetup/GameSetup.h @@ -29,6 +29,7 @@ * initialize global modules that are be needed before Init. * must be called from the very beginning of main. **/ +extern void EarliestInit(); extern void EarlyInit(); extern void EndGame(); Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -361,7 +361,7 @@ g_RenderingOptions.ClearHooks(); - g_Profiler2.ShutdownGPU(); + g_Profiler2->ShutdownGPU(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); @@ -480,7 +480,7 @@ } #endif -void EarlyInit() +void EarliestInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); @@ -493,11 +493,10 @@ debug_filter_add("FILES"); timer_Init(); +} - // initialise profiler early so it can profile startup, - // but only after LatchStartTime - g_Profiler2.Initialise(); - +void EarlyInit() +{ FixLocales(); // Because we do GL calls from a secondary thread, Xlib needs to @@ -614,7 +613,7 @@ bool profilerHTTPEnable = false; CFG_GET_VAL("profiler2.autoenable", profilerHTTPEnable); if (profilerHTTPEnable) - g_Profiler2.EnableHTTP(); + g_Profiler2->EnableHTTP(); // Initialise everything except Win32 sockets (because our networking // system already inits those) @@ -646,7 +645,7 @@ bool profilerGPUEnable = false; CFG_GET_VAL("profiler2.autoenable", profilerGPUEnable); if (profilerGPUEnable) - g_Profiler2.EnableGPU(); + g_Profiler2->EnableGPU(); if(!g_Quickstart) { Index: source/ps/Profiler2.h =================================================================== --- source/ps/Profiler2.h +++ source/ps/Profiler2.h @@ -30,7 +30,7 @@ * which are designed more for measuring average throughput over a number of * frames. * - * To view the profiler output, press F11 to enable the HTTP output mode + * To view the profiler output, press Ctrl + F11 to enable the HTTP output mode * and then open source/tools/profiler2/profiler2.html in a web browser. * * There is a single global CProfiler2 instance (g_Profiler2), @@ -47,12 +47,12 @@ * called hundreds of times per frame. (The old CProfilerManager is better * for that.) * - * New threads must call g_Profiler2.RegisterCurrentThread before any other + * New threads must call g_Profiler2->RegisterCurrentThread before any other * profiler functions. * - * The main thread should call g_Profiler2.RecordFrameStart at the start of + * The main thread should call g_Profiler2->RecordFrameStart at the start of * each frame. - * Other threads should call g_Profiler2.RecordSyncMarker occasionally, + * Other threads should call g_Profiler2->RecordSyncMarker occasionally, * especially if it's been a long time since their last call to the profiler, * or if they've made thousands of calls since the last sync marker. * @@ -70,7 +70,7 @@ * and to simplify the visualisation of the data by doing it externally in an * environment with better UI tools (i.e. HTML) instead of within the game engine. * - * The initial setup of g_Profiler2 must happen in the game's main thread. + * CProfiler2 must be constructed and destructed in the game's main thread. * RegisterCurrentThread and the Record functions may be called from any thread. * The HTTP server runs its own threads, which may call the ConstructJSON functions. */ @@ -272,12 +272,6 @@ CProfiler2(); ~CProfiler2(); - /** - * Call in main thread to set up the profiler, - * before calling any other profiler functions. - */ - void Initialise(); - /** * Call in main thread to enable the HTTP server. * (Disabled by default for security and performance @@ -307,12 +301,6 @@ */ void Toggle(); - /** - * Call in main thread to shut everything down. - * All other profiled threads should have been terminated already. - */ - void Shutdown(); - /** * Call in any thread to enable the profiler in that thread. * @p name should be unique, and is used by the visualiser to identify @@ -444,13 +432,11 @@ return *m_CurrentStorage; } - bool m_Initialised; - - int m_FrameNumber; + int m_FrameNumber = 0; - mg_context* m_MgContext; + mg_context* m_MgContext = nullptr; - CProfiler2GPU* m_GPU; + CProfiler2GPU* m_GPU = nullptr; std::mutex m_Mutex; @@ -458,7 +444,7 @@ std::vector> m_Threads; // thread-safe; protected by m_Mutex }; -extern CProfiler2 g_Profiler2; +extern CProfiler2* g_Profiler2; /** * Scope-based enter/leave helper. @@ -468,11 +454,11 @@ public: CProfile2Region(const char* name) : m_Name(name) { - g_Profiler2.RecordRegionEnter(m_Name); + g_Profiler2->RecordRegionEnter(m_Name); } ~CProfile2Region() { - g_Profiler2.RecordRegionLeave(); + g_Profiler2->RecordRegionLeave(); } protected: const char* m_Name; @@ -516,11 +502,11 @@ public: CProfile2GPURegion(const char* name) : m_Name(name) { - g_Profiler2.RecordGPURegionEnter(m_Name); + g_Profiler2->RecordGPURegionEnter(m_Name); } ~CProfile2GPURegion() { - g_Profiler2.RecordGPURegionLeave(m_Name); + g_Profiler2->RecordGPURegionLeave(m_Name); } private: const char* m_Name; @@ -544,7 +530,7 @@ /** * Record the named event at the current time. */ -#define PROFILE2_EVENT(name) g_Profiler2.RecordEvent(name) +#define PROFILE2_EVENT(name) g_Profiler2->RecordEvent(name) /** * Associates a string (with printf-style formatting) with the current @@ -552,6 +538,6 @@ * (If the last profiler call was PROFILE2_EVENT, it associates with that * event; otherwise it associates with the currently-active region.) */ -#define PROFILE2_ATTR g_Profiler2.RecordAttribute +#define PROFILE2_ATTR g_Profiler2->RecordAttribute #endif // INCLUDED_PROFILER2 Index: source/ps/Profiler2.cpp =================================================================== --- source/ps/Profiler2.cpp +++ source/ps/Profiler2.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -40,7 +40,7 @@ #include #include -CProfiler2 g_Profiler2; +CProfiler2* g_Profiler2 = nullptr; const size_t CProfiler2::MAX_ATTRIBUTE_LENGTH = 256; @@ -53,15 +53,27 @@ thread_local CProfiler2::ThreadStorage* CProfiler2::m_CurrentStorage = nullptr; -CProfiler2::CProfiler2() : - m_Initialised(false), m_FrameNumber(0), m_MgContext(NULL), m_GPU(NULL) +CProfiler2::CProfiler2() { + ENSURE(Threading::IsMainThread()); + ENSURE(!g_Profiler2); + + RegisterCurrentThread("main"); + g_Profiler2 = this; } CProfiler2::~CProfiler2() { - if (m_Initialised) - Shutdown(); + ENSURE(Threading::IsMainThread()); + // Must shutdown GPU before profiler. + ENSURE(!m_GPU); + + g_Profiler2 = nullptr; + + if (m_MgContext) + { + mg_stop(m_MgContext); + } } /** @@ -159,14 +171,6 @@ } }; -void CProfiler2::Initialise() -{ - ENSURE(!m_Initialised); - m_Initialised = true; - - RegisterCurrentThread("main"); -} - void CProfiler2::InitialiseGPU() { ENSURE(!m_GPU); @@ -175,7 +179,6 @@ void CProfiler2::EnableHTTP() { - ENSURE(m_Initialised); LOGMESSAGERENDER("Starting profiler2 HTTP server"); // Ignore multiple enablings @@ -204,7 +207,6 @@ void CProfiler2::EnableGPU() { - ENSURE(m_Initialised); if (!m_GPU) { LOGMESSAGERENDER("Starting profiler2 GPU mode"); @@ -243,24 +245,6 @@ } } -void CProfiler2::Shutdown() -{ - ENSURE(m_Initialised); - - ENSURE(!m_GPU); // must shutdown GPU before profiler - - if (m_MgContext) - { - mg_stop(m_MgContext); - m_MgContext = NULL; - } - - // the destructor is not called for the main thread - // we have to call it manually to avoid memory leaks - ENSURE(Threading::IsMainThread()); - m_Initialised = false; -} - void CProfiler2::RecordGPUFrameStart() { if (m_GPU) @@ -287,8 +271,6 @@ void CProfiler2::RegisterCurrentThread(const std::string& name) { - ENSURE(m_Initialised); - // Must not register a thread more than once. ENSURE(m_CurrentStorage == nullptr); @@ -960,41 +942,41 @@ CProfile2SpikeRegion::CProfile2SpikeRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) { - if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) - g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE); + if (g_Profiler2->HoldLevel() < 8 && g_Profiler2->HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) + g_Profiler2->HoldMessages(CProfiler2::ThreadStorage::BUFFER_SPIKE); else m_PushedHold = false; COMPILER_FENCE; - g_Profiler2.RecordRegionEnter(m_Name); - m_StartTime = g_Profiler2.GetTime(); + g_Profiler2->RecordRegionEnter(m_Name); + m_StartTime = g_Profiler2->GetTime(); } CProfile2SpikeRegion::~CProfile2SpikeRegion() { - double time = g_Profiler2.GetTime(); - g_Profiler2.RecordRegionLeave(); + double time = g_Profiler2->GetTime(); + g_Profiler2->RecordRegionLeave(); bool shouldWrite = time - m_StartTime > m_Limit; if (m_PushedHold) - g_Profiler2.StopHoldingMessages(shouldWrite); + g_Profiler2->StopHoldingMessages(shouldWrite); } CProfile2AggregatedRegion::CProfile2AggregatedRegion(const char* name, double spikeLimit) : m_Name(name), m_Limit(spikeLimit), m_PushedHold(true) { - if (g_Profiler2.HoldLevel() < 8 && g_Profiler2.HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) - g_Profiler2.HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE); + if (g_Profiler2->HoldLevel() < 8 && g_Profiler2->HoldType() != CProfiler2::ThreadStorage::BUFFER_AGGREGATE) + g_Profiler2->HoldMessages(CProfiler2::ThreadStorage::BUFFER_AGGREGATE); else m_PushedHold = false; COMPILER_FENCE; - g_Profiler2.RecordRegionEnter(m_Name); - m_StartTime = g_Profiler2.GetTime(); + g_Profiler2->RecordRegionEnter(m_Name); + m_StartTime = g_Profiler2->GetTime(); } CProfile2AggregatedRegion::~CProfile2AggregatedRegion() { - double time = g_Profiler2.GetTime(); - g_Profiler2.RecordRegionLeave(); + double time = g_Profiler2->GetTime(); + g_Profiler2->RecordRegionLeave(); bool shouldWrite = time - m_StartTime > m_Limit; if (m_PushedHold) - g_Profiler2.StopHoldingMessages(shouldWrite, true); + g_Profiler2->StopHoldingMessages(shouldWrite, true); } Index: source/ps/Replay.cpp =================================================================== --- source/ps/Replay.cpp +++ source/ps/Replay.cpp @@ -291,10 +291,10 @@ else if (type == "end") { { - g_Profiler2.RecordFrameStart(); + g_Profiler2->RecordFrameStart(); PROFILE2("frame"); - g_Profiler2.IncrementFrameNumber(); - PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); + g_Profiler2->IncrementFrameNumber(); + PROFILE2_ATTR("%d", g_Profiler2->GetFrameNumber()); g_Game->GetSimulation2()->Update(turnLength, commands); commands.clear(); @@ -312,7 +312,7 @@ SAFE_DELETE(m_Stream); - g_Profiler2.SaveToFile(); + g_Profiler2->SaveToFile(); std::string hash; bool ok = g_Game->GetSimulation2()->ComputeStateHash(hash, false); Index: source/ps/TaskManager.cpp =================================================================== --- source/ps/TaskManager.cpp +++ source/ps/TaskManager.cpp @@ -275,7 +275,7 @@ static std::atomic n = 0; std::string name = "Task Mgr #" + std::to_string(n++); debug_SetThreadName(name.c_str()); - g_Profiler2.RegisterCurrentThread(name); + g_Profiler2->RegisterCurrentThread(name); std::function task; Index: source/ps/UserReport.cpp =================================================================== --- source/ps/UserReport.cpp +++ source/ps/UserReport.cpp @@ -230,7 +230,7 @@ static void RunThread(CUserReporterWorker* data) { debug_SetThreadName("CUserReportWorker"); - g_Profiler2.RegisterCurrentThread("userreport"); + g_Profiler2->RegisterCurrentThread("userreport"); data->Run(); } @@ -269,13 +269,13 @@ // Wait until the main thread wakes us up while (true) { - g_Profiler2.RecordRegionEnter("condition_variable wait"); + g_Profiler2->RecordRegionEnter("condition_variable wait"); std::unique_lock lock(m_WorkerMutex); m_WorkerCV.wait(lock); lock.unlock(); - g_Profiler2.RecordRegionLeave(); + g_Profiler2->RecordRegionLeave(); // Handle shutdown requests as soon as possible if (GetShutdown()) Index: source/renderer/Renderer.cpp =================================================================== --- source/renderer/Renderer.cpp +++ source/renderer/Renderer.cpp @@ -438,7 +438,7 @@ { PROFILE3("render"); - g_Profiler2.RecordGPUFrameStart(); + g_Profiler2->RecordGPUFrameStart(); g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get()); @@ -486,7 +486,7 @@ PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats); PROFILE2_ATTR("particles: %zu", stats.m_Particles); - g_Profiler2.RecordGPUFrameEnd(); + g_Profiler2->RecordGPUFrameEnd(); } void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger) Index: source/scriptinterface/ScriptContext.cpp =================================================================== --- source/scriptinterface/ScriptContext.cpp +++ source/scriptinterface/ScriptContext.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -41,13 +41,13 @@ { if (CProfileManager::IsInitialised() && Threading::IsMainThread()) g_Profiler.Start("GCSlice"); - g_Profiler2.RecordRegionEnter("GCSlice"); + g_Profiler2->RecordRegionEnter("GCSlice"); } else if (progress == JS::GC_SLICE_END) { if (CProfileManager::IsInitialised() && Threading::IsMainThread()) g_Profiler.Stop(); - g_Profiler2.RecordRegionLeave(); + g_Profiler2->RecordRegionLeave(); } // The following code can be used to print some information aobut garbage collection Index: source/scriptinterface/ScriptInterface.cpp =================================================================== --- source/scriptinterface/ScriptInterface.cpp +++ source/scriptinterface/ScriptInterface.cpp @@ -233,7 +233,7 @@ if (CProfileManager::IsInitialised() && Threading::IsMainThread()) g_Profiler.StartScript(name); - g_Profiler2.RecordRegionEnter(name); + g_Profiler2->RecordRegionEnter(name); } void ProfileStop() @@ -241,7 +241,7 @@ if (CProfileManager::IsInitialised() && Threading::IsMainThread()) g_Profiler.Stop(); - g_Profiler2.RecordRegionLeave(); + g_Profiler2->RecordRegionLeave(); } void ProfileAttribute(const std::string& attr) @@ -257,7 +257,7 @@ if (!attr.empty()) name = StringFlyweight(attr).get().c_str(); - g_Profiler2.RecordAttribute("%s", name); + g_Profiler2->RecordAttribute("%s", name); } // Math override functions: Index: source/soundmanager/SoundManager.cpp =================================================================== --- source/soundmanager/SoundManager.cpp +++ source/soundmanager/SoundManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -108,7 +108,7 @@ static void RunThread(CSoundManagerWorker* data) { debug_SetThreadName("CSoundManagerWorker"); - g_Profiler2.RegisterCurrentThread("soundmanager"); + g_Profiler2->RegisterCurrentThread("soundmanager"); data->Run(); } Index: source/test_setup.cpp =================================================================== --- source/test_setup.cpp +++ source/test_setup.cpp @@ -24,8 +24,6 @@ #include "precompiled.h" -#include - #include "lib/self_test.h" #include @@ -42,6 +40,9 @@ #include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptInterface.h" +#include +#include + class LeakReporter : public CxxTest::GlobalFixture { virtual bool tearDownWorld() @@ -81,7 +82,7 @@ Threading::SetMainThread(); - g_Profiler2.Initialise(); + profiler = std::make_unique(); m_ScriptEngine = new ScriptEngine; g_ScriptContext = ScriptContext::CreateContext(); @@ -95,7 +96,7 @@ Threading::TaskManager::Instance().ClearQueue(); g_ScriptContext.reset(); SAFE_DELETE(m_ScriptEngine); - g_Profiler2.Shutdown(); + profiler.reset(); return true; } @@ -109,6 +110,7 @@ } private: + std::unique_ptr profiler; // We're doing the initialization and shutdown of the ScriptEngine explicitly here // to make sure it's only initialized when setUpWorld is called.