diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 583e96a21..15f4f5a16 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -168,6 +168,7 @@ set(agcDriverSources ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PipelineCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Resources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Recorder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ParallelCompare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderInputState.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ShaderAddressResources.cpp @@ -752,6 +753,7 @@ add_test_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL Graphics/src/UnitShadow.cpp Graphics/src/Sampler.cpp Graphics/src/Recorder.cpp + Graphics/src/ParallelCompare.cpp Graphics/src/ShaderInputState.cpp ${agcTextureDetileShaderHeader} ${agcCmaskClearShaderHeader} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp new file mode 100644 index 000000000..a996ab230 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp @@ -0,0 +1,22 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_PARALLELCOMPARE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_PARALLELCOMPARE_HPP + +#include +#include +#include + +namespace AgcDriver::Graphics { + +struct CompareSpan { + const void* first; + const void* second; + std::size_t bytes; +}; + +void CompareSpans(std::span spans, std::span equal); +void CompareSpansFrom(std::span spans, std::span equal, std::size_t parallelBytes); +unsigned CompareHelpers(); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ParallelCompare.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ParallelCompare.cpp new file mode 100644 index 000000000..b4fce7184 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ParallelCompare.cpp @@ -0,0 +1,104 @@ +#include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp" +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { +namespace { + +class ComparePool { +public: + explicit ComparePool(unsigned count) { + for (unsigned i = 0; i < count; ++i) std::thread([this] { work(); }).detach(); + } + + bool Run(std::span spans, std::span equal) { + std::unique_lock caller(callers, std::try_to_lock); + if (!caller.owns_lock()) return false; + { + std::unique_lock lock(mutex); + finished.wait(lock, [&] { return working == 0; }); + jobSpans = spans; + jobEqual = equal; + next.store(0, std::memory_order_relaxed); + done.store(0, std::memory_order_relaxed); + ++generation; + } + wake.notify_all(); + drain(spans, equal); + std::unique_lock lock(mutex); + finished.wait(lock, [&] { return working == 0 && done.load(std::memory_order_acquire) == spans.size(); }); + jobSpans = {}; + jobEqual = {}; + return true; + } + +private: + void drain(std::span spans, std::span equal) { + std::size_t compared = 0; + for (auto index = next.fetch_add(1, std::memory_order_relaxed); index < spans.size(); index = next.fetch_add(1, std::memory_order_relaxed)) { + const auto& span = spans[index]; + equal[index] = std::memcmp(span.first, span.second, span.bytes) == 0 ? 1 : 0; + ++compared; + } + if (compared != 0) done.fetch_add(compared, std::memory_order_acq_rel); + } + + void work() { + std::uint64_t seen = 0; + std::unique_lock lock(mutex); + for (;;) { + wake.wait(lock, [&] { return generation != seen; }); + seen = generation; + const auto spans = jobSpans; + const auto equal = jobEqual; + ++working; + lock.unlock(); + drain(spans, equal); + lock.lock(); + --working; + if (working == 0) finished.notify_all(); + } + } + + std::mutex callers; + std::mutex mutex; + std::condition_variable wake; + std::condition_variable finished; + std::uint64_t generation = 0; + unsigned working = 0; + std::span jobSpans; + std::span jobEqual; + std::atomic next{0}; + std::atomic done{0}; +}; + +ComparePool* sharedPool() { + static ComparePool* const pool = new ComparePool(CompareHelpers()); + return pool; +} + +} + +unsigned CompareHelpers() { + static const unsigned helpers = std::clamp(std::thread::hardware_concurrency() / 8u, 1u, 4u); + return helpers; +} + +void CompareSpansFrom(std::span spans, std::span equal, std::size_t parallelBytes) { + std::size_t total = 0; + for (const auto& span : spans) total += span.bytes; + if (spans.size() > 1 && total >= parallelBytes) { + if (sharedPool()->Run(spans, equal)) return; + } + for (std::size_t index = 0; index < spans.size(); ++index) equal[index] = std::memcmp(spans[index].first, spans[index].second, spans[index].bytes) == 0 ? 1 : 0; +} + +void CompareSpans(std::span spans, std::span equal) { + CompareSpansFrom(spans, equal, std::size_t{1} << 20u); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 8702aa6a0..d85e5516b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/shaders/MeshArguments_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/SampleCounter_spv.h" @@ -2488,9 +2489,12 @@ bool Recorder::refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, s thread_local std::vector generations; thread_local std::vector changed; thread_local std::vector> differing; + thread_local std::vector spans; + thread_local std::vector equal; generations.assign(count, entry.generation); changed.assign(count, GuestMemory::BlockWritten); differing.clear(); + spans.clear(); const bool sole = entry.buffer.use_count() == 1; GuestMemory::ChangedBlocks(address, bytes, generations, changed); for (std::size_t k = 0; k < count; ++k) { @@ -2498,8 +2502,14 @@ bool Recorder::refreshDrawSnapshot(DrawSnapshot& entry, std::uint64_t address, s const auto from = std::max(address, aligned + k * block); const auto to = std::min(end, aligned + (k + 1) * block); const auto offset = static_cast(from - address); - const auto length = static_cast(to - from); - if (std::memcmp(held.data() + offset, reinterpret_cast(from), length) == 0) continue; + spans.push_back({held.data() + offset, reinterpret_cast(from), static_cast(to - from)}); + } + equal.assign(spans.size(), 0); + CompareSpans(spans, equal); + for (std::size_t k = 0; k < spans.size(); ++k) { + if (equal[k] != 0) continue; + const auto offset = static_cast(static_cast(spans[k].first) - held.data()); + const auto length = spans[k].bytes; if (!sole) return false; if (!differing.empty() && differing.back().first + differing.back().second == offset) differing.back().second += length; else differing.emplace_back(offset, length); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 3c38d8e39..21c943821 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -9,6 +9,7 @@ #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ParallelCompare.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "SceShaders.hpp" @@ -33,6 +34,7 @@ #include #include #include +#include #include namespace { @@ -3890,6 +3892,41 @@ void vertexZeroFillMergeTests() { Require(solo.size() == 1 && solo[0] == 1, "a fetch past the records is copied alone"); } +void parallelCompareTests() { + using AgcDriver::Graphics::CompareSpan; + using AgcDriver::Graphics::CompareSpansFrom; + constexpr std::size_t block = 65536; + constexpr std::size_t count = 48; + std::vector first(block * count + 100, std::byte{7}); + auto second = first; + const std::array changedAt{3 * block + 17, 20 * block, 33 * block + block - 1, 47 * block + 120}; + for (const auto at : changedAt) second[at] = std::byte{9}; + std::vector spans; + for (std::size_t k = 0; k < count; ++k) { + const auto length = k + 1 == count ? block + 100 : block; + spans.push_back({first.data() + k * block, second.data() + k * block, length}); + } + const auto expected = [&](std::size_t k) { + return std::none_of(changedAt.begin(), changedAt.end(), [&](std::size_t at) { return at >= k * block && at < k * block + spans[k].bytes; }); + }; + for (const std::size_t threshold : {std::size_t{0}, std::size_t{1} << 40u}) { + for (int round = 0; round < 20; ++round) { + std::vector equal(count, 2); + CompareSpansFrom(spans, equal, threshold); + for (std::size_t k = 0; k < count; ++k) Require((equal[k] == 1) == expected(k) && equal[k] <= 1, "parallel compare misreported a span"); + } + } + std::vector left(count, 2), right(count, 2); + std::thread other([&] { + for (int round = 0; round < 50; ++round) CompareSpansFrom(spans, right, 0); + }); + for (int round = 0; round < 50; ++round) CompareSpansFrom(spans, left, 0); + other.join(); + for (std::size_t k = 0; k < count; ++k) Require((left[k] == 1) == expected(k) && (right[k] == 1) == expected(k), "concurrent parallel compares misreported a span"); + std::vector none; + CompareSpansFrom({}, none, 0); +} + int main() { #ifdef _WIN32 _putenv_s("APS5_PIN_WAIT_MS", "200"); @@ -3981,6 +4018,7 @@ int main() { nullVertexDescriptorTests(); vertexZeroFillTests(); vertexZeroFillMergeTests(); + parallelCompareTests(); pixelParameterSlotTests(); rectListTests(); floatControlsModeTests();