diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index 74c0e6a9c..795336a40 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -106,6 +106,9 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_compile_options(-mfloat-abi=hard) endif() endif() + if(EMSCRIPTEN) + add_compile_options(-msimd128) + endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_compile_options(/Zc:__cplusplus) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/cmake/SfizzSIMDSourceFiles.cmake b/cmake/SfizzSIMDSourceFiles.cmake index 9e588e93e..1d7c571bb 100644 --- a/cmake/SfizzSIMDSourceFiles.cmake +++ b/cmake/SfizzSIMDSourceFiles.cmake @@ -9,7 +9,7 @@ macro(sfizz_add_simd_sources SOURCES_VAR PREFIX) # For CPU-dispatched X86 sources # Always build them for all X86 targets. - if(SFIZZ_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64|x64|X64|i.86|x86|X86)$") + if(SFIZZ_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64|x64|X64|i.86|x86|X86)$" AND NOT EMSCRIPTEN) # on GCC, it requires to set ISA support flags on individual files # to be able to use the intrinsics if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/external/atomic_queue/include/atomic_queue/defs.h b/external/atomic_queue/include/atomic_queue/defs.h index a279dcf8d..18f6b79c1 100644 --- a/external/atomic_queue/include/atomic_queue/defs.h +++ b/external/atomic_queue/include/atomic_queue/defs.h @@ -36,6 +36,13 @@ static inline void spin_loop_pause() noexcept { #endif } } // namespace atomic_queue +#elif defined(EMSCRIPTEN) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; +static inline void spin_loop_pause() noexcept { + // No-op +} +} // namespace atomic_queue #else #error "Unknown CPU architecture." #endif diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index 28675eb2a..af7c884e6 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -5,7 +5,6 @@ #include "SIMDHelpers.h" #include "utility/SwapAndPop.h" #include -#include sfz::EQHolder::EQHolder(Resources& resources) : resources(resources) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index e15be0f40..d5b4f323f 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -31,43 +31,14 @@ #include "Config.h" #include "utility/SwapAndPop.h" #include "utility/Debug.h" -#include #include #include #include #include #include -#include #include -#if defined(_WIN32) -#include -#else -#include -#endif -using namespace std::placeholders; -static std::weak_ptr globalThreadPoolWeakPtr; -static std::mutex globalThreadPoolMutex; - -static std::shared_ptr globalThreadPool() -{ - std::shared_ptr threadPool; - - threadPool = globalThreadPoolWeakPtr.lock(); - if (threadPool) - return threadPool; - - std::lock_guard lock(globalThreadPoolMutex); - threadPool = globalThreadPoolWeakPtr.lock(); - if (threadPool) - return threadPool; - - unsigned numThreads = std::thread::hardware_concurrency(); - numThreads = (numThreads > 2) ? (numThreads - 2) : 1; - threadPool.reset(new ThreadPool(numThreads)); - globalThreadPoolWeakPtr = threadPool; - return threadPool; -} +using namespace std::placeholders; void readBaseFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, uint32_t numFrames) { @@ -141,28 +112,12 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std: } sfz::FilePool::FilePool() - : filesToLoad(alignedNew()), - threadPool(globalThreadPool()) { - loadingJobs.reserve(config::maxVoices); - lastUsedFiles.reserve(config::maxVoices); - garbageToCollect.reserve(config::maxVoices); } sfz::FilePool::~FilePool() { - std::error_code ec; - - garbageFlag = false; - semGarbageBarrier.post(ec); - garbageThread.join(); - - dispatchFlag = false; - dispatchBarrier.post(ec); - dispatchThread.join(); - for (auto& job : loadingJobs) - job.wait(); } bool sfz::FilePool::checkSample(std::string& filename) const noexcept @@ -284,10 +239,6 @@ absl::optional sfz::FilePool::checkExistingFileInformation if (loadedFile != loadedFiles.end()) return loadedFile->second.information; - const auto preloadedFile = preloadedFiles.find(fileId); - if (preloadedFile != preloadedFiles.end()) - return preloadedFile->second.information; - return {}; } @@ -308,68 +259,17 @@ absl::optional sfz::FilePool::getFileInformation(const Fil bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexcept { - const auto loadedFile = loadedFiles.find(fileId); - if (loadedFile != loadedFiles.end()) { - loadedFile->second.preloadCallCount++; - return true; - } - - auto fileInformation = getFileInformation(fileId); - if (!fileInformation) - return false; - - fileInformation->maxOffset = maxOffset; - const fs::path file { rootDirectory / fileId.filename() }; - AudioReaderPtr reader = createAudioReader(file, fileId.isReverse()); - - const auto frames = static_cast(reader->frames()); - const auto framesToLoad = [&]() { - if (loadInRam) - return frames; - else - return min(frames, maxOffset + preloadSize); - }(); - - const auto existingFile = preloadedFiles.find(fileId); - if (existingFile != preloadedFiles.end()) { - if (framesToLoad > existingFile->second.preloadedData.getNumFrames()) { - preloadedFiles[fileId].information.maxOffset = maxOffset; - preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad); - } - existingFile->second.preloadCallCount++; - } else { - fileInformation->sampleRate = static_cast(reader->sampleRate()); - auto insertedPair = preloadedFiles.insert_or_assign(fileId, { - readFromFile(*reader, framesToLoad), - *fileInformation - }); - - insertedPair.first->second.status = FileData::Status::Preloaded; - insertedPair.first->second.preloadCallCount++; - } - - return true; + return static_cast(loadFile(fileId)); } void sfz::FilePool::resetPreloadCallCounts() noexcept { - for (auto& preloadedFile: preloadedFiles) - preloadedFile.second.preloadCallCount = 0; - for (auto& loadedFile: loadedFiles) loadedFile.second.preloadCallCount = 0; } void sfz::FilePool::removeUnusedPreloadedData() noexcept { - for (auto it = preloadedFiles.begin(), end = preloadedFiles.end(); it != end; ) { - auto copyIt = it++; - if (copyIt->second.preloadCallCount == 0) { - DBG("[sfizz] Removing unused preloaded data: " << copyIt->first.filename()); - preloadedFiles.erase(copyIt); - } - } - for (auto it = loadedFiles.begin(), end = loadedFiles.end(); it != end; ) { auto copyIt = it++; if (copyIt->second.preloadCallCount == 0) { @@ -426,100 +326,21 @@ sfz::FileDataHolder sfz::FilePool::loadFromRam(const FileId& fileId, const std:: sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr& fileId) noexcept { const auto loaded = loadedFiles.find(*fileId); - if (loaded != loadedFiles.end()) - return { &loaded->second }; - - const auto preloaded = preloadedFiles.find(*fileId); - if (preloaded == preloadedFiles.end()) { - DBG("[sfizz] File not found in the preloaded files: " << fileId->filename()); - return {}; - } - QueuedFileData queuedData { fileId, &preloaded->second }; - if (!filesToLoad->try_push(queuedData)) { - DBG("[sfizz] Could not enqueue the file to load for " << fileId << " (queue capacity " << filesToLoad->capacity() << ")"); + if (loaded == loadedFiles.end()) { + DBG("[sfizz] File not found in the loaded files: " << fileId->filename()); return {}; } - std::error_code ec; - dispatchBarrier.post(ec); - ASSERT(!ec); - - return { &preloaded->second }; + return { &loaded->second }; } void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept { - this->preloadSize = preloadSize; - if (loadInRam) - return; - - // Update all the preloaded sizes - for (auto& preloadedFile : preloadedFiles) { - const auto maxOffset = preloadedFile.second.information.maxOffset; - fs::path file { rootDirectory / preloadedFile.first.filename() }; - AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); - preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset); - } -} - -void sfz::FilePool::loadingJob(const QueuedFileData& data) noexcept -{ - raiseCurrentThreadPriority(); - - std::shared_ptr id = data.id.lock(); - if (!id) { - // file ID was nulled, it means the region was deleted, ignore - return; - } - - const fs::path file { rootDirectory / id->filename() }; - std::error_code readError; - AudioReaderPtr reader = createAudioReader(file, id->isReverse(), &readError); - - if (readError) { - DBG("[sfizz] libsndfile errored for " << *id << " with message " << readError.message()); - return; - } - - FileData::Status currentStatus = data.data->status.load(); - - unsigned spinCounter { 0 }; - while (currentStatus == FileData::Status::Invalid) { - // Spin until the state changes - if (spinCounter > 1024) { - DBG("[sfizz] " << *id << " is stuck on Invalid? Leaving the load"); - return; - } - - std::this_thread::sleep_for(std::chrono::microseconds(100)); - currentStatus = data.data->status.load(); - spinCounter += 1; - } - - // Already loading or loaded - if (currentStatus != FileData::Status::Preloaded) - return; - - // Someone else got the token - if (!data.data->status.compare_exchange_strong(currentStatus, FileData::Status::Streaming)) - return; - - streamFromFile(*reader, data.data->fileData, &data.data->availableFrames); - - data.data->status = FileData::Status::Done; - - std::lock_guard guard { garbageAndLastUsedMutex }; - if (absl::c_find(lastUsedFiles, *id) == lastUsedFiles.end()) - lastUsedFiles.push_back(*id); + // NOOP } void sfz::FilePool::clear() { - std::lock_guard guard { garbageAndLastUsedMutex }; - emptyFileLoadingQueues(); - garbageToCollect.clear(); - lastUsedFiles.clear(); - preloadedFiles.clear(); loadedFiles.clear(); } @@ -528,143 +349,17 @@ uint32_t sfz::FilePool::getPreloadSize() const noexcept return preloadSize; } -template -bool is_ready(std::future const& f) -{ - return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; -} - -void sfz::FilePool::dispatchingJob() noexcept -{ - while (dispatchBarrier.wait(), dispatchFlag) { - std::lock_guard guard { loadingJobsMutex }; - - QueuedFileData queuedData; - if (filesToLoad->try_pop(queuedData)) { - if (queuedData.id.expired()) { - // file ID was nulled, it means the region was deleted, ignore - } - else - loadingJobs.push_back( - threadPool->enqueue([this](const QueuedFileData& data) { loadingJob(data); }, std::move(queuedData))); - } - - // Clear finished jobs - swapAndPopAll(loadingJobs, [](std::future& future) { - return is_ready(future); - }); - } -} - -void sfz::FilePool::garbageJob() noexcept -{ - while (semGarbageBarrier.wait(), garbageFlag) { - std::lock_guard guard { garbageAndLastUsedMutex }; - garbageToCollect.clear(); - } -} - void sfz::FilePool::waitForBackgroundLoading() noexcept { - std::lock_guard guard { loadingJobsMutex }; - - for (auto& job : loadingJobs) - job.wait(); - - loadingJobs.clear(); -} - -void sfz::FilePool::raiseCurrentThreadPriority() noexcept -{ -#if defined(_WIN32) - HANDLE thread = GetCurrentThread(); - const int priority = THREAD_PRIORITY_ABOVE_NORMAL; /*THREAD_PRIORITY_HIGHEST*/ - if (!SetThreadPriority(thread, priority)) { - std::system_error error(GetLastError(), std::system_category()); - DBG("[sfizz] Cannot set current thread priority: " << error.what()); - } -#else - pthread_t thread = pthread_self(); - int policy; - sched_param param; - - if (pthread_getschedparam(thread, &policy, ¶m) != 0) { - DBG("[sfizz] Cannot get current thread scheduling parameters"); - return; - } - - policy = SCHED_RR; - const int minprio = sched_get_priority_min(policy); - const int maxprio = sched_get_priority_max(policy); - param.sched_priority = minprio + config::backgroundLoaderPthreadPriority * (maxprio - minprio) / 100; - - if (pthread_setschedparam(thread, policy, ¶m) != 0) { - DBG("[sfizz] Cannot set current thread scheduling parameters"); - return; - } -#endif + // NOOP } void sfz::FilePool::setRamLoading(bool loadInRam) noexcept { - if (loadInRam == this->loadInRam) - return; - - this->loadInRam = loadInRam; - - if (loadInRam) { - for (auto& preloadedFile : preloadedFiles) { - fs::path file { rootDirectory / preloadedFile.first.filename() }; - AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); - preloadedFile.second.preloadedData = readFromFile( - *reader, - preloadedFile.second.information.end - ); - } - } else { - setPreloadSize(preloadSize); - } + // NOOP } void sfz::FilePool::triggerGarbageCollection() noexcept { - const std::unique_lock guard { garbageAndLastUsedMutex, std::try_to_lock }; - if (!guard.owns_lock()) - return; - - const auto now = std::chrono::high_resolution_clock::now(); - swapAndPopAll(lastUsedFiles, [&](const FileId& id) { - if (garbageToCollect.size() == garbageToCollect.capacity()) - return false; - - auto it = preloadedFiles.find(id); - if (it == preloadedFiles.end()) { - // Getting here means that the preloadedFiles got changed (probably cleared) - // while the lastUsedFiles were untouched. - return true; - } - - sfz::FileData& data = it->second; - if (data.status == FileData::Status::Preloaded) - return true; - - if (data.status != FileData::Status::Done) - return false; - - if (data.readerCount != 0) - return false; - - const auto secondsIdle = std::chrono::duration_cast(now - data.lastViewerLeftAt).count(); - if (secondsIdle < config::fileClearingPeriod) - return false; - - data.availableFrames = 0; - data.status = FileData::Status::Preloaded; - garbageToCollect.push_back(std::move(data.fileData)); - return true; - }); - - std::error_code ec; - semGarbageBarrier.post(ec); - ASSERT(!ec); + // NOOP } diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index f76e2783f..c62a40432 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -26,13 +26,11 @@ #pragma once #include "Config.h" #include "Defaults.h" -#include "RTSemaphore.h" #include "AudioBuffer.h" #include "AudioSpan.h" #include "FileId.h" #include "FileMetadata.h" #include "SIMDHelpers.h" -#include "SpinMutex.h" #include "utility/Timing.h" #include "utility/LeakDetector.h" #include "utility/MemoryHelpers.h" @@ -40,12 +38,8 @@ #include #include #include -#include #include -#include -#include #include -class ThreadPool; namespace sfz { using FileAudioBuffer = AudioBuffer& fileId) noexcept; /** - * @brief Change the preloading size. This will trigger a full - * reload of all samples, so don't call it on the audio thread. + * @brief Change the preloading size. (ineffective here) * * @param preloadSize */ @@ -302,8 +281,7 @@ class FilePool { uint32_t getPreloadSize() const noexcept; /** * @brief Empty the file loading queues without actually loading - * the files. All promises will be unfulfilled. Don't call this - * method on the audio thread as it will spinlock. + * the files. * */ void emptyFileLoadingQueues() noexcept @@ -316,11 +294,6 @@ class FilePool { * in the queue. */ void waitForBackgroundLoading() noexcept; - /** - * @brief Assign the current thread a priority which is appropriate - * for background sample file processing. - */ - static void raiseCurrentThreadPriority() noexcept; /** * @brief Change whether all samples are loaded in ram. * This will trigger a purge and reloading. @@ -339,44 +312,9 @@ class FilePool { absl::optional checkExistingFileInformation(const FileId& fileId) noexcept; fs::path rootDirectory; - bool loadInRam { config::loadInRam }; uint32_t preloadSize { config::preloadSize }; - // Signals - volatile bool dispatchFlag { true }; - volatile bool garbageFlag { true }; - RTSemaphore dispatchBarrier; - RTSemaphore semGarbageBarrier; - - // Structures for the background loaders - struct QueuedFileData - { - QueuedFileData() noexcept {} - QueuedFileData(std::weak_ptr id, FileData* data) noexcept - : id(id), data(data) {} - std::weak_ptr id; - FileData* data { nullptr }; - }; - - using FileQueue = atomic_queue::AtomicQueue2; - aligned_unique_ptr filesToLoad; - - void dispatchingJob() noexcept; - void garbageJob() noexcept; - void loadingJob(const QueuedFileData& data) noexcept; - std::mutex loadingJobsMutex; - std::vector> loadingJobs; - std::thread dispatchThread { &FilePool::dispatchingJob, this }; - std::thread garbageThread { &FilePool::garbageJob, this }; - - SpinMutex garbageAndLastUsedMutex; - std::vector lastUsedFiles; - std::vector garbageToCollect; - - std::shared_ptr threadPool; - // Preloaded data - absl::flat_hash_map preloadedFiles; absl::flat_hash_map loadedFiles; LEAK_DETECTOR(FilePool); }; diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 8fbd356d5..a0b6ecd89 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -5,7 +5,6 @@ #include "SIMDHelpers.h" #include "utility/SwapAndPop.h" #include -#include #include sfz::FilterHolder::FilterHolder(Resources& resources)