diff --git a/renderdoc/common/jobsystem_tests.cpp b/renderdoc/common/jobsystem_tests.cpp new file mode 100644 index 000000000..14f4dceb6 --- /dev/null +++ b/renderdoc/common/jobsystem_tests.cpp @@ -0,0 +1,185 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "threading.h" + +namespace Threading +{ +extern uint32_t randomSleepRange; +extern uint32_t randomSpinRange; +}; + +#if ENABLED(ENABLE_UNIT_TESTS) + +#include "catch/catch.hpp" + +bool isSorted(const rdcarray &arr) +{ + if(arr.size() <= 1) + return true; + + for(size_t i = 0; i < arr.size() - 1; i++) + { + if(arr[i] > arr[i + 1]) + return false; + } + + return true; +} + +// helper function - we don't do this with multiple sections to avoid permuting the thread init/shutdown too much +void RunJobTests() +{ + // one job + { + bool flag = false; + Threading::JobSystem::AddJob([&flag]() { flag = true; }); + + Threading::JobSystem::SyncAllJobs(); + + CHECK(flag); + } + + // many jobs + { + static const size_t numJobs = 5000; + static const size_t numItems = 500; + rdcarray a[numJobs]; + + for(size_t j = 0; j < numJobs; j++) + { + a[j].reserve(numItems); + for(size_t i = 0; i < numItems; i++) + a[j].push_back(rand()); + + Threading::JobSystem::AddJob([&a, j]() { std::sort(a[j].begin(), a[j].end()); }); + } + + Threading::JobSystem::SyncAllJobs(); + + for(size_t j = 0; j < numJobs; j++) + CHECK(isSorted(a[j])); + } + + // one job and dependency + { + rdcarray a; + Threading::JobSystem::Job *first = Threading::JobSystem::AddJob([&a]() { a.push_back(1); }); + Threading::JobSystem::AddJob([&a]() { a.push_back(2); }, {first}); + + Threading::JobSystem::SyncAllJobs(); + + CHECK(a == rdcarray({1, 2})); + } + + // long dependency chain + { + rdcarray a; + rdcarray b; + + rdcarray parents; + for(int i = 0; i < 100; i++) + { + parents = {Threading::JobSystem::AddJob([&a, i]() { a.push_back(i); }, parents)}; + + b.push_back(i); + } + + Threading::JobSystem::SyncAllJobs(); + + CHECK(a == b); + } + + // multiple dependency chains + { + static const size_t numChains = 50; + rdcarray a[numChains]; + rdcarray b[numChains]; + + rdcarray parents[numChains]; + for(int i = 0; i < 100; i++) + { + for(size_t c = 0; c < numChains; c++) + { + parents[c] = {Threading::JobSystem::AddJob([&a, c, i]() { a[c].push_back(i); }, parents[c])}; + + b[c].push_back(i); + } + } + + Threading::JobSystem::SyncAllJobs(); + + for(size_t c = 0; c < numChains; c++) + CHECK(a[c] == b[c]); + } +} + +TEST_CASE("Check job system behaviour is correct with common thread counts", "[jobs]") +{ + Threading::randomSleepRange = 10; + Threading::randomSpinRange = 1000; + uint32_t numThreads = GENERATE(1, 2, 14, 24); + + Threading::JobSystem::Init(numThreads); + + RunJobTests(); + + Threading::JobSystem::Shutdown(); + + // start up and shut down again a couple of times to ensure that works as well + + Threading::JobSystem::Init(numThreads); + + Threading::JobSystem::Shutdown(); + + Threading::JobSystem::Init(numThreads); + + Threading::JobSystem::Shutdown(); +} + +// since lock contention can get really bad with many threads, only do this test once +TEST_CASE("Stress test job system with many threads", "[jobs][stress]") +{ + Threading::randomSleepRange = 2; + Threading::randomSpinRange = 100; + uint32_t numThreads = 1000; + + Threading::JobSystem::Init(numThreads); + + RunJobTests(); + + Threading::JobSystem::Shutdown(); + + // start up and shut down again a couple of times to ensure that works as well + + Threading::JobSystem::Init(numThreads); + + Threading::JobSystem::Shutdown(); + + Threading::JobSystem::Init(numThreads); + + Threading::JobSystem::Shutdown(); +} + +#endif // ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index 8c96b1dc1..0fdb18c8c 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -533,6 +533,7 @@ + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index cd770385d..2b46f5971 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -965,6 +965,9 @@ Common + + Common +