Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2665,6 +2665,11 @@ if(BUILD_TESTING)
add_test(NAME agc_shader_srt_read_patching COMMAND agc_shader_memory_tests --srt-read-patching)
set_tests_properties(agc_shader_srt_read_patching PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_table_snapshot_reuse COMMAND agc_shader_memory_tests --table-snapshot-reuse)
add_test(NAME agc_shader_region_merge COMMAND agc_shader_memory_tests --region-merge)
set_tests_properties(agc_shader_region_merge PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_shader_region_merge PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_shader_table_snapshot_reuse PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_table_snapshot_reuse_unmasked COMMAND agc_shader_memory_tests --table-snapshot-reuse)
set_tests_properties(agc_shader_table_snapshot_reuse_unmasked PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_MASKED_RESULT_MEMO=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
Expand Down
23 changes: 19 additions & 4 deletions core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -787,24 +787,35 @@ std::vector<ShaderRecompiler::MemoryRegion> ShaderMemory::collect(bool recentOnl
std::vector<Cursor> cursors;
std::size_t runs = 0;
for (const auto& entry : supplied) {
if (recentOnly && !entry.recent) continue;
if ((recentOnly && !entry.recent) || entry.words->runs.empty()) continue;
cursors.push_back({entry.words.get(), 0u, 0u});
runs += entry.words->runs.size() + entry.words->words.size() / PageWords;
}
std::vector<ShaderRecompiler::MemoryRegion> result;
result.reserve((recentOnly ? 0u : initial.size()) + pages.size() + runs);
auto next = recentOnly ? initial.end() : initial.begin();
const bool heap = cursors.size() > 1u;
const auto later = [](const Cursor& left, const Cursor& right) {
return left.words->runs[left.run].first > right.words->runs[right.run].first;
};
if (heap) std::make_heap(cursors.begin(), cursors.end(), later);
const auto emitBefore = [&](std::uint64_t address) {
while (true) {
Cursor* lowest = nullptr;
for (auto& cursor : cursors) {
if (cursor.run < cursor.words->runs.size() && (lowest == nullptr || cursor.words->runs[cursor.run].first < lowest->words->runs[lowest->run].first)) lowest = &cursor;
Cursor* lowest = heap && !cursors.empty() ? &cursors.front() : nullptr;
if (!heap) {
for (auto& cursor : cursors) {
if (cursor.run < cursor.words->runs.size() && (lowest == nullptr || cursor.words->runs[cursor.run].first < lowest->words->runs[lowest->run].first)) lowest = &cursor;
}
}
const auto run = lowest != nullptr ? lowest->words->runs[lowest->run].first : std::numeric_limits<std::uint64_t>::max();
if (next != initial.end() && next->first < address && next->first < run) {
result.push_back({next->first, next->second});
++next;
} else if (lowest != nullptr && run < address) {
if (heap) {
std::pop_heap(cursors.begin(), cursors.end(), later);
lowest = &cursors.back();
}
const auto count = lowest->words->runs[lowest->run].second;
const auto words = std::span(lowest->words->words).subspan(lowest->at, count);
for (std::size_t done = 0; done < count;) {
Expand All @@ -815,6 +826,10 @@ std::vector<ShaderRecompiler::MemoryRegion> ShaderMemory::collect(bool recentOnl
}
lowest->at += count;
++lowest->run;
if (heap) {
if (lowest->run == lowest->words->runs.size()) cursors.pop_back();
else std::push_heap(cursors.begin(), cursors.end(), later);
}
} else {
return;
}
Expand Down
79 changes: 79 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2582,6 +2582,80 @@ void ProtectTablePage(std::uintptr_t page, std::uint32_t protection) {
GuestAllocations::GuestAllocationsInvalidate_nid_postfix(page, 4096u);
}

void verifyRegionMerge(bool strided = false, bool benchmark = false) {
using namespace ShaderRecompiler;
constexpr std::size_t SlotBytes = 8192;
constexpr std::size_t TableBytes = 4096;
const std::size_t stride = strided ? 64u : 32u;
constexpr std::size_t MaximumBlocks = 64;
constexpr std::size_t Bytes = (MaximumBlocks + 8u) * SlotBytes;
auto* heap = AllocateWatchedTable(Bytes);
struct Release {
void* pointer;
~Release() { GuestArena::GuestArenaRelease_nid_postfix(pointer, Bytes); }
} release{heap};
std::vector<TableGuest> guests(MaximumBlocks);
std::vector<std::array<std::uint32_t, 2>> userData(MaximumBlocks);
for (std::size_t block = 0; block < MaximumBlocks; ++block) {
auto* table = heap + block * SlotBytes / sizeof(std::uint32_t);
for (std::size_t record = 0; record < TableBytes / stride; ++record) {
const auto words = TableT(reinterpret_cast<const void*>(0x100000000ull + record * 256u), TableFormat8888UNorm, TableType2D, 1u);
std::copy(words.begin(), words.end(), table + record * stride / sizeof(std::uint32_t));
}
FillTableSrt(guests[block], table, stride, TableBytes / stride);
userData[block] = TableUserData(guests[block]);
}
const bool watched = AgcDriver::GuestMemory::CollectWrites(reinterpret_cast<std::uintptr_t>(heap), Bytes) != 0u;
for (const std::size_t count : {0u, 1u, 2u, 3u, 7u, 8u, 16u, 64u}) {
std::vector<MemoryRegion> initial;
for (std::size_t block = 0; block <= count; ++block) {
for (std::size_t offset = TableBytes; offset < SlotBytes; offset += 64u) {
auto* word = heap + (block * SlotBytes + offset) / sizeof(std::uint32_t);
*word = static_cast<std::uint32_t>(block * SlotBytes + offset);
initial.push_back({reinterpret_cast<std::uintptr_t>(word), std::as_bytes(std::span(word, 1))});
}
}
AgcDriver::ShaderMemory memory(initial);
for (std::size_t position = 0; position < count; ++position) {
const auto block = (position * (count > 1u ? count - 1u : 1u)) % count;
const auto request = TableRequest(TableWholeCode, userData[block]);
{
AgcDriver::ShaderMemory warm({});
static_cast<void>(warm.Capture(request));
}
static_cast<void>(memory.Capture(request));
}
if (watched) require(memory.SuppliedBlocks().size() == count, "region merge: the fixture did not retain every shared table");
const auto verify = [&](const std::vector<MemoryRegion>& regions, bool recent) {
for (std::size_t index = 0; index < regions.size(); ++index) {
const auto& region = regions[index];
require(index == 0u || regions[index - 1u].guestAddress + regions[index - 1u].bytes.size() <= region.guestAddress, "region merge: regions were reordered or duplicated");
require(std::memcmp(region.bytes.data(), reinterpret_cast<const void*>(region.guestAddress), region.bytes.size()) == 0, "region merge: collected words changed");
}
for (std::size_t block = 0; block < count; ++block) {
for (std::size_t record = 0; record < TableBytes / stride; ++record) require(TableCovered(regions, heap + (block * SlotBytes + record * stride) / sizeof(std::uint32_t), 32u), "region merge: a shared table record was omitted");
}
for (const auto& entry : initial) require(TableCovered(regions, reinterpret_cast<const std::uint32_t*>(entry.guestAddress), entry.bytes.size()) != recent, "region merge: initial-region visibility changed");
};
const auto regions = memory.Regions();
verify(regions, false);
verify(memory.TakeRecentRegions(), true);
require(memory.TakeRecentRegions().empty(), "region merge: recent regions were not reset");
verify(memory.Regions(), false);
if (!strided && benchmark) {
constexpr std::size_t Iterations = 1000;
std::size_t total = 0;
const auto started = std::chrono::steady_clock::now();
for (std::size_t iteration = 0; iteration < Iterations; ++iteration) total += memory.Regions().size();
const auto elapsed = std::chrono::duration<double, std::micro>(std::chrono::steady_clock::now() - started).count();
require(total == Iterations * regions.size(), "region merge: repeated collection changed coverage");
std::printf("region-merge blocks=%zu regions=%zu iterations=%zu us_per_call=%.3f\n", count, regions.size(), Iterations, elapsed / Iterations);
}
}
std::puts("region merge: mixed initial, captured and shared regions retain order, coverage and recent-read semantics");
}


void verifyTableSnapshotReuse() {
using namespace ShaderRecompiler;
namespace Abi = ImageTableAbi;
Expand Down Expand Up @@ -9344,6 +9418,11 @@ int main(int argc, char** argv) {
std::cout << "The result memo budget counts and trims the masked table's entries\n";
return 0;
}
if (argc == 2 && (std::string_view(argv[1]) == "--region-merge" || std::string_view(argv[1]) == "--region-merge-bench")) {
verifyRegionMerge(false, std::string_view(argv[1]) == "--region-merge-bench");
verifyRegionMerge(true);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--result-memo-budget-threads") {
verifyResultMemoBudgetThreads();
std::cout << "The result memo ends within its byte budget when results are inserted during a trim\n";
Expand Down
Loading