Index: source/graphics/TextureConverter.h =================================================================== --- source/graphics/TextureConverter.h +++ source/graphics/TextureConverter.h @@ -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 @@ -22,10 +22,10 @@ #include "TextureManager.h" -#include +#if CONFIG2_NVTT +#include "ps/Future.h" #include -#include -#include +#endif class MD5; @@ -202,23 +202,18 @@ bool IsBusy(); private: - static void RunThread(CTextureConverter* data); - PIVFS m_VFS; bool m_HighQuality; #if CONFIG2_NVTT - std::thread m_WorkerThread; - std::mutex m_WorkerMutex; - std::condition_variable m_WorkerCV; -#endif // CONFIG2_NVTT - struct ConversionRequest; struct ConversionResult; - std::deque> m_RequestQueue; // protected by m_WorkerMutex - std::deque> m_ResultQueue; // protected by m_WorkerMutex - bool m_Shutdown; // protected by m_WorkerMutex + // Completely arbitrary constant - there is some main-thread cost to loading textures, so probably + // should not be too high. Some results may already be ready. + static constexpr std::deque>::size_type MAX_QUEUE_SIZE = 6; + std::deque> m_ResultQueue; +#endif }; #endif // INCLUDED_TEXTURECONVERTER Index: source/graphics/TextureConverter.cpp =================================================================== --- source/graphics/TextureConverter.cpp +++ source/graphics/TextureConverter.cpp @@ -28,7 +28,7 @@ #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profiler2.h" -#include "ps/Threading.h" +#include "ps/TaskManager.h" #include "ps/Util.h" #include "ps/XML/Xeromyces.h" @@ -293,42 +293,20 @@ } CTextureConverter::CTextureConverter(PIVFS vfs, bool highQuality) : - m_VFS(vfs), m_HighQuality(highQuality), m_Shutdown(false) + m_VFS(vfs), m_HighQuality(highQuality) { #if CONFIG2_NVTT // Verify that we are running with at least the version we were compiled with, // to avoid bugs caused by ABI changes ENSURE(nvtt::version() >= NVTT_VERSION); - - m_WorkerThread = std::thread(Threading::HandleExceptions::Wrapper, this); - - // Maybe we should share some centralised pool of worker threads? - // For now we'll just stick with a single thread for this specific use. #endif // CONFIG2_NVTT } CTextureConverter::~CTextureConverter() { #if CONFIG2_NVTT - // Tell the thread to shut down - { - std::lock_guard lock(m_WorkerMutex); - m_Shutdown = true; - } - - while (true) - { - // Wake the thread up so that it shutdowns. - // If we send the message once, there is a chance it will be missed, - // so keep sending until shtudown becomes false again, indicating that the thread has shut down. - std::lock_guard lock(m_WorkerMutex); - m_WorkerCV.notify_all(); - if (!m_Shutdown) - break; - } - - // Wait for it to shut down cleanly - m_WorkerThread.join(); + for (Future& res : m_ResultQueue) + res.CancelOrWait(); #endif // CONFIG2_NVTT } @@ -478,13 +456,23 @@ delete[] rgba; } + m_ResultQueue.emplace_back(Threading::TaskManager::Instance().PushTask([request] { - std::lock_guard lock(m_WorkerMutex); - m_RequestQueue.push_back(request); - } + PROFILE2("convert texture"); + // Set up the result object + ConversionResult result; + result.dest = request->dest; + result.texture = request->texture; + + request->outputOptions.setOutputHandler(&result.output); - // Wake up the worker thread - m_WorkerCV.notify_all(); + // Perform the compression + nvtt::Compressor compressor; + result.ret = compressor.process(request->inputOptions, request->compressionOptions, + request->outputOptions); + + return result; + }, Threading::TaskPriority::LOW)); return true; @@ -497,25 +485,16 @@ bool CTextureConverter::Poll(CTexturePtr& texture, VfsPath& dest, bool& ok) { #if CONFIG2_NVTT - std::shared_ptr result; - - // Grab the first result (if any) - { - std::lock_guard lock(m_WorkerMutex); - if (!m_ResultQueue.empty()) - { - result = m_ResultQueue.front(); - m_ResultQueue.pop_front(); - } - } - - if (!result) + if (m_ResultQueue.empty() || !m_ResultQueue.front().IsReady()) { // no work to do return false; } - if (!result->ret) + ConversionResult result = m_ResultQueue.front().Get(); + m_ResultQueue.pop_front(); + + if (!result.ret) { // conversion had failed ok = false; @@ -523,11 +502,11 @@ } // Move output into a correctly-aligned buffer - size_t size = result->output.buffer.size(); + size_t size = result.output.buffer.size(); std::shared_ptr file; AllocateAligned(file, size, maxSectorSize); - memcpy(file.get(), &result->output.buffer[0], size); - if (m_VFS->CreateFile(result->dest, file, size) < 0) + memcpy(file.get(), &result.output.buffer[0], size); + if (m_VFS->CreateFile(result.dest, file, size) < 0) { // error writing file ok = false; @@ -535,8 +514,8 @@ } // Succeeded in converting texture - texture = result->texture; - dest = result->dest; + texture = result.texture; + dest = result.dest; ok = true; return true; @@ -548,66 +527,8 @@ bool CTextureConverter::IsBusy() { #if CONFIG2_NVTT - std::lock_guard lock(m_WorkerMutex); - return !m_RequestQueue.empty(); + return m_ResultQueue.size() >= MAX_QUEUE_SIZE; #else // CONFIG2_NVTT return false; #endif // !CONFIG2_NVTT } - -void CTextureConverter::RunThread(CTextureConverter* textureConverter) -{ -#if CONFIG2_NVTT - debug_SetThreadName("TextureConverter"); - g_Profiler2.RegisterCurrentThread("texconv"); - // Wait until the main thread wakes us up - while (true) - { - // We may have several textures in the incoming queue, process them all before going back to sleep. - if (!textureConverter->IsBusy()) { - std::unique_lock wait_lock(textureConverter->m_WorkerMutex); - // Use the no-condition variant because spurious wake-ups don't matter that much here. - textureConverter->m_WorkerCV.wait(wait_lock); - } - - g_Profiler2.RecordSyncMarker(); - PROFILE2_EVENT("wakeup"); - - std::shared_ptr request; - - { - std::lock_guard wait_lock(textureConverter->m_WorkerMutex); - if (textureConverter->m_Shutdown) - break; - // If we weren't woken up for shutdown, we must have been woken up for - // a new request, so grab it from the queue - request = textureConverter->m_RequestQueue.front(); - textureConverter->m_RequestQueue.pop_front(); - } - - // Set up the result object - std::shared_ptr result = std::make_shared(); - result->dest = request->dest; - result->texture = request->texture; - - request->outputOptions.setOutputHandler(&result->output); - -// TIMER(L"TextureConverter compress"); - - { - PROFILE2("compress"); - - // Perform the compression - nvtt::Compressor compressor; - result->ret = compressor.process(request->inputOptions, request->compressionOptions, request->outputOptions); - } - - // Push the result onto the queue - std::lock_guard wait_lock(textureConverter->m_WorkerMutex); - textureConverter->m_ResultQueue.push_back(result); - } - - std::lock_guard wait_lock(textureConverter->m_WorkerMutex); - textureConverter->m_Shutdown = false; -#endif // CONFIG2_NVTT -} Index: source/lib/path.h =================================================================== --- source/lib/path.h +++ source/lib/path.h @@ -92,6 +92,8 @@ DetectSeparator(); } + Path(Path&&) = default; + Path(const char* p) : path((const unsigned char*)p, (const unsigned char*)p+strlen(p)) // interpret bytes as unsigned; makes no difference for ASCII,