Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 27063) +++ ps/trunk/source/ps/GameSetup/HWDetect.cpp (revision 27064) @@ -1,249 +1,257 @@ /* 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 * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/svn_revision.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/external_libraries/libsdl.h" #include "lib/posix/posix_utsname.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/numa.h" #include "lib/sysdep/os_cpu.h" #if CONFIG2_AUDIO #include "soundmanager/SoundManager.h" #endif #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/scripting/JSInterface_ConfigDB.h" #include "ps/scripting/JSInterface_Debug.h" #include "ps/UserReport.h" #include "ps/VideoMode.h" #include "renderer/backend/IDevice.h" #include "scriptinterface/FunctionWrapper.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" // FreeType headers might have an include order. #include #include #if OS_LINUX #include #endif #include #include +#include static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings); static void ReportFreeType(const ScriptRequest& rq, JS::HandleValue settings); void SetDisableAudio(bool disabled) { g_DisableAudio = disabled; } void RunHardwareDetection() { TIMER(L"RunHardwareDetection"); ScriptInterface scriptInterface("Engine", "HWDetect", g_ScriptContext); ScriptRequest rq(scriptInterface); JSI_Debug::RegisterScriptFunctions(scriptInterface); // Engine.DisplayErrorDialog JSI_ConfigDB::RegisterScriptFunctions(scriptInterface); ScriptFunction::Register(rq, "SetDisableAudio"); // Load the detection script: const wchar_t* scriptName = L"hwdetect/hwdetect.js"; CVFSFile file; if (file.Load(g_VFS, scriptName) != PSRETURN_OK) { LOGERROR("Failed to load hardware detection script"); return; } std::string code = file.DecodeUTF8(); // assume it's UTF-8 scriptInterface.LoadScript(scriptName, code); // Collect all the settings we'll pass to the script: // (We'll use this same data for the opt-in online reporting system, so it // includes some fields that aren't directly useful for the hwdetect script) JS::RootedValue settings(rq.cx); Script::CreateObject(rq, &settings); Script::SetProperty(rq, settings, "os_unix", OS_UNIX); Script::SetProperty(rq, settings, "os_bsd", OS_BSD); Script::SetProperty(rq, settings, "os_linux", OS_LINUX); Script::SetProperty(rq, settings, "os_android", OS_ANDROID); Script::SetProperty(rq, settings, "os_macosx", OS_MACOSX); Script::SetProperty(rq, settings, "os_win", OS_WIN); Script::SetProperty(rq, settings, "arch_ia32", ARCH_IA32); Script::SetProperty(rq, settings, "arch_amd64", ARCH_AMD64); Script::SetProperty(rq, settings, "arch_arm", ARCH_ARM); Script::SetProperty(rq, settings, "arch_aarch64", ARCH_AARCH64); Script::SetProperty(rq, settings, "arch_e2k", ARCH_E2K); Script::SetProperty(rq, settings, "arch_ppc64", ARCH_PPC64); #ifdef NDEBUG Script::SetProperty(rq, settings, "build_debug", 0); #else Script::SetProperty(rq, settings, "build_debug", 1); #endif Script::SetProperty(rq, settings, "build_opengles", CONFIG2_GLES); Script::SetProperty(rq, settings, "build_datetime", std::string(__DATE__ " " __TIME__)); Script::SetProperty(rq, settings, "build_revision", std::wstring(svn_revision)); Script::SetProperty(rq, settings, "build_msc", (int)MSC_VERSION); Script::SetProperty(rq, settings, "build_icc", (int)ICC_VERSION); Script::SetProperty(rq, settings, "build_gcc", (int)GCC_VERSION); Script::SetProperty(rq, settings, "build_clang", (int)CLANG_VERSION); Script::SetProperty(rq, settings, "gfx_card", g_VideoMode.GetBackendDevice()->GetName()); Script::SetProperty(rq, settings, "gfx_drv_ver", g_VideoMode.GetBackendDevice()->GetDriverInformation()); #if CONFIG2_AUDIO if (g_SoundManager) { Script::SetProperty(rq, settings, "snd_card", g_SoundManager->GetSoundCardNames()); Script::SetProperty(rq, settings, "snd_drv_ver", g_SoundManager->GetOpenALVersion()); } #endif ReportSDL(rq, settings); ReportFreeType(rq, settings); JS::RootedValue backendDeviceSettings(rq.cx); Script::CreateObject(rq, &backendDeviceSettings); g_VideoMode.GetBackendDevice()->Report(rq, backendDeviceSettings); Script::SetProperty(rq, settings, "renderer_backend", backendDeviceSettings); Script::SetProperty(rq, settings, "video_desktop_xres", g_VideoMode.GetDesktopXRes()); Script::SetProperty(rq, settings, "video_desktop_yres", g_VideoMode.GetDesktopYRes()); Script::SetProperty(rq, settings, "video_desktop_bpp", g_VideoMode.GetDesktopBPP()); Script::SetProperty(rq, settings, "video_desktop_freq", g_VideoMode.GetDesktopFreq()); struct utsname un; uname(&un); Script::SetProperty(rq, settings, "uname_sysname", std::string(un.sysname)); Script::SetProperty(rq, settings, "uname_release", std::string(un.release)); Script::SetProperty(rq, settings, "uname_version", std::string(un.version)); Script::SetProperty(rq, settings, "uname_machine", std::string(un.machine)); #if OS_LINUX { std::ifstream ifs("/etc/lsb-release"); if (ifs.good()) { std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); Script::SetProperty(rq, settings, "linux_release", str); } } #endif Script::SetProperty(rq, settings, "cpu_identifier", std::string(cpu_IdentifierString())); Script::SetProperty(rq, settings, "cpu_frequency", os_cpu_ClockFrequency()); Script::SetProperty(rq, settings, "cpu_pagesize", (u32)os_cpu_PageSize()); Script::SetProperty(rq, settings, "cpu_largepagesize", (u32)os_cpu_LargePageSize()); Script::SetProperty(rq, settings, "cpu_numprocs", (u32)os_cpu_NumProcessors()); Script::SetProperty(rq, settings, "numa_numnodes", (u32)numa_NumNodes()); Script::SetProperty(rq, settings, "numa_factor", numa_Factor()); Script::SetProperty(rq, settings, "numa_interleaved", numa_IsMemoryInterleaved()); Script::SetProperty(rq, settings, "ram_total", (u32)os_cpu_MemorySize()); Script::SetProperty(rq, settings, "ram_total_os", (u32)os_cpu_QueryMemorySize()); #if ARCH_X86_X64 Script::SetProperty(rq, settings, "x86_vendor", (u32)x86_x64::Vendor()); Script::SetProperty(rq, settings, "x86_model", (u32)x86_x64::Model()); Script::SetProperty(rq, settings, "x86_family", (u32)x86_x64::Family()); u32 caps0, caps1, caps2, caps3; x86_x64::GetCapBits(&caps0, &caps1, &caps2, &caps3); Script::SetProperty(rq, settings, "x86_caps[0]", caps0); Script::SetProperty(rq, settings, "x86_caps[1]", caps1); Script::SetProperty(rq, settings, "x86_caps[2]", caps2); Script::SetProperty(rq, settings, "x86_caps[3]", caps3); #endif Script::SetProperty(rq, settings, "timer_resolution", timer_Resolution()); + Script::SetProperty(rq, settings, "hardware_concurrency", std::thread::hardware_concurrency()); + // The version should be increased for every meaningful change. - const int reportVersion = 19; + const int reportVersion = 20; // Send the same data to the reporting system g_UserReporter.SubmitReport( "hwdetect", reportVersion, Script::StringifyJSON(rq, &settings, false), Script::StringifyJSON(rq, &settings, true)); // Run the detection script: JS::RootedValue global(rq.cx, rq.globalValue()); ScriptFunction::CallVoid(rq, global, "RunHardwareDetection", settings); } static void ReportSDL(const ScriptRequest& rq, JS::HandleValue settings) { SDL_version build, runtime; SDL_VERSION(&build); char version[16]; snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", build.major, build.minor, build.patch); Script::SetProperty(rq, settings, "sdl_build_version", version); SDL_GetVersion(&runtime); snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", runtime.major, runtime.minor, runtime.patch); Script::SetProperty(rq, settings, "sdl_runtime_version", version); // This is null in atlas (and further the call triggers an assertion). const char* backend = g_VideoMode.GetWindow() ? GetSDLSubsystem(g_VideoMode.GetWindow()) : "none"; Script::SetProperty(rq, settings, "sdl_video_backend", backend ? backend : "unknown"); + + Script::SetProperty(rq, settings, "sdl_display_count", SDL_GetNumVideoDisplays()); + + Script::SetProperty(rq, settings, "sdl_cpu_count", SDL_GetCPUCount()); + Script::SetProperty(rq, settings, "sdl_system_ram", SDL_GetSystemRAM()); } static void ReportFreeType(const ScriptRequest& rq, JS::HandleValue settings) { FT_Library FTLibrary; std::string FTSupport = "unsupported"; if (!FT_Init_FreeType(&FTLibrary)) { FT_Int major, minor, patch; FT_Library_Version(FTLibrary, &major, &minor, &patch); FT_Done_FreeType(FTLibrary); std::stringstream version; version << major << "." << minor << "." << patch; FTSupport = version.str(); } else { FTSupport = "cantload"; } Script::SetProperty(rq, settings, "freetype", FTSupport); } Index: ps/trunk/source/ps/TaskManager.cpp =================================================================== --- ps/trunk/source/ps/TaskManager.cpp (revision 27063) +++ ps/trunk/source/ps/TaskManager.cpp (revision 27064) @@ -1,311 +1,322 @@ /* 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 * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "TaskManager.h" #include "lib/debug.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Threading.h" #include "ps/ThreadUtil.h" #include "ps/Profiler2.h" #include #include #include #include #include #include namespace Threading { + +namespace +{ /** * Minimum number of TaskManager workers. */ -static constexpr size_t MIN_THREADS = 3; +constexpr size_t MIN_WORKERS = 3; /** * Maximum number of TaskManager workers. */ -static constexpr size_t MAX_THREADS = 32; +constexpr size_t MAX_WORKERS = 32; + +size_t GetDefaultNumberOfWorkers() +{ + const size_t hardware_concurrency = std::thread::hardware_concurrency(); + return hardware_concurrency ? Clamp(hardware_concurrency - 1, MIN_WORKERS, MAX_WORKERS) : MIN_WORKERS; +} + +} // anonymous namespace std::unique_ptr g_TaskManager; class Thread; using QueueItem = std::function; /** * Light wrapper around std::thread. Ensures Join has been called. */ class Thread { public: Thread() = default; Thread(const Thread&) = delete; Thread(Thread&&) = delete; template void Start(T* object) { m_Thread = std::thread(HandleExceptions>::Wrapper, object); } template static void DoStart(T* object); protected: ~Thread() { ENSURE(!m_Thread.joinable()); } std::thread m_Thread; std::atomic m_Kill = false; }; /** * Worker thread: process the taskManager queues until killed. */ class WorkerThread : public Thread { public: WorkerThread(TaskManager::Impl& taskManager); ~WorkerThread(); /** * Wake the worker. */ void Wake(); protected: void RunUntilDeath(); std::mutex m_Mutex; std::condition_variable m_ConditionVariable; TaskManager::Impl& m_TaskManager; }; /** * PImpl-ed implementation of the Task manager. * * The normal priority queue is processed first, the low priority only if there are no higher-priority tasks */ class TaskManager::Impl { friend class TaskManager; friend class WorkerThread; public: Impl(TaskManager& backref); ~Impl() { ClearQueue(); m_Workers.clear(); } /** * 2-phase init to avoid having to think too hard about the order of class members. */ void SetupWorkers(size_t numberOfWorkers); /** * Push a task on the global queue. * Takes ownership of @a task. * May be called from any thread. */ void PushTask(std::function&& task, TaskPriority priority); protected: void ClearQueue(); template bool PopTask(std::function& taskOut); // Back reference (keep this first). TaskManager& m_TaskManager; std::atomic m_HasWork = false; std::atomic m_HasLowPriorityWork = false; std::mutex m_GlobalMutex; std::mutex m_GlobalLowPriorityMutex; std::deque m_GlobalQueue; std::deque m_GlobalLowPriorityQueue; // Ideally this would be a vector, since it does get iterated, but that requires movable types. std::deque m_Workers; // Round-robin counter for GetWorker. mutable size_t m_RoundRobinIdx = 0; }; -TaskManager::TaskManager() : TaskManager(std::thread::hardware_concurrency() - 1) +TaskManager::TaskManager() : TaskManager(GetDefaultNumberOfWorkers()) { } TaskManager::TaskManager(size_t numberOfWorkers) { m = std::make_unique(*this); - numberOfWorkers = Clamp(numberOfWorkers, MIN_THREADS, MAX_THREADS); + numberOfWorkers = Clamp(numberOfWorkers, MIN_WORKERS, MAX_WORKERS); m->SetupWorkers(numberOfWorkers); } TaskManager::~TaskManager() = default; TaskManager::Impl::Impl(TaskManager& backref) : m_TaskManager(backref) { } void TaskManager::Impl::SetupWorkers(size_t numberOfWorkers) { for (size_t i = 0; i < numberOfWorkers; ++i) m_Workers.emplace_back(*this); } void TaskManager::ClearQueue() { m->ClearQueue(); } void TaskManager::Impl::ClearQueue() { { std::lock_guard lock(m_GlobalMutex); m_GlobalQueue.clear(); } { std::lock_guard lock(m_GlobalLowPriorityMutex); m_GlobalLowPriorityQueue.clear(); } } size_t TaskManager::GetNumberOfWorkers() const { return m->m_Workers.size(); } void TaskManager::DoPushTask(std::function&& task, TaskPriority priority) { m->PushTask(std::move(task), priority); } void TaskManager::Impl::PushTask(std::function&& task, TaskPriority priority) { std::mutex& mutex = priority == TaskPriority::NORMAL ? m_GlobalMutex : m_GlobalLowPriorityMutex; std::deque& queue = priority == TaskPriority::NORMAL ? m_GlobalQueue : m_GlobalLowPriorityQueue; std::atomic& hasWork = priority == TaskPriority::NORMAL ? m_HasWork : m_HasLowPriorityWork; { std::lock_guard lock(mutex); queue.emplace_back(std::move(task)); hasWork = true; } for (WorkerThread& worker : m_Workers) worker.Wake(); } template bool TaskManager::Impl::PopTask(std::function& taskOut) { std::mutex& mutex = Priority == TaskPriority::NORMAL ? m_GlobalMutex : m_GlobalLowPriorityMutex; std::deque& queue = Priority == TaskPriority::NORMAL ? m_GlobalQueue : m_GlobalLowPriorityQueue; std::atomic& hasWork = Priority == TaskPriority::NORMAL ? m_HasWork : m_HasLowPriorityWork; // Particularly critical section since we're locking the global queue. std::lock_guard globalLock(mutex); if (!queue.empty()) { taskOut = std::move(queue.front()); queue.pop_front(); hasWork = !queue.empty(); return true; } return false; } void TaskManager::Initialise() { if (!g_TaskManager) g_TaskManager = std::make_unique(); } TaskManager& TaskManager::Instance() { ENSURE(g_TaskManager); return *g_TaskManager; } // Thread definition WorkerThread::WorkerThread(TaskManager::Impl& taskManager) : m_TaskManager(taskManager) { Start(this); } WorkerThread::~WorkerThread() { m_Kill = true; m_ConditionVariable.notify_one(); if (m_Thread.joinable()) m_Thread.join(); } void WorkerThread::Wake() { m_ConditionVariable.notify_one(); } void WorkerThread::RunUntilDeath() { // The profiler does better if the names are unique. static std::atomic n = 0; std::string name = "Task Mgr #" + std::to_string(n++); debug_SetThreadName(name.c_str()); g_Profiler2.RegisterCurrentThread(name); std::function task; bool hasTask = false; std::unique_lock lock(m_Mutex, std::defer_lock); while (!m_Kill) { lock.lock(); m_ConditionVariable.wait(lock, [this](){ return m_Kill || m_TaskManager.m_HasWork || m_TaskManager.m_HasLowPriorityWork; }); lock.unlock(); if (m_Kill) break; // Fetch work from the global queues. hasTask = m_TaskManager.PopTask(task); if (!hasTask) hasTask = m_TaskManager.PopTask(task); if (hasTask) task(); } } // Defined here - needs access to derived types. template void Thread::DoStart(T* object) { std::invoke(callable, object); } } // namespace Threading