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
8 changes: 8 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,14 @@ if(BUILD_TESTING)
set_tests_properties(agc_host_import_second_mapping PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_pending_key_stores COMMAND agc_driver_recorder_tests --pending-key-stores-only)
set_tests_properties(agc_pending_key_stores PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_shadowed_key_stores COMMAND agc_driver_recorder_tests --shadowed-key-stores-only)
set_tests_properties(agc_shadowed_key_stores PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_pending_key_stores agc_shadowed_key_stores PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp)
Expand Down
92 changes: 57 additions & 35 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,48 @@ void NoteKeysFillOnGpu(std::uint64_t begin, std::size_t count, DccKeys keys) {

namespace {

struct LandedKeys {
int value = -1;
bool mixed = false;
bool unknown = false;
};

LandedKeys KeysAfterPendingWrites(const Recorder& recorder, std::uint64_t begin, std::size_t count) {
auto writes = recorder.PendingWritesOver(begin, count);
std::sort(writes.begin(), writes.end(), [](const Recorder::PendingWrite& a, const Recorder::PendingWrite& b) { return a.note > b.note; });
std::vector<std::pair<std::uint64_t, std::uint64_t>> unwritten{{begin, begin + count}};
LandedKeys landed;
const auto see = [&](int key) {
if (landed.value < 0) landed.value = key;
else if (landed.value != key) landed.mixed = true;
};
for (const auto& write : writes) {
if (landed.mixed || unwritten.empty()) break;
std::vector<std::pair<std::uint64_t, std::uint64_t>> rest;
bool covered = false;
for (const auto& [from, to] : unwritten) {
if (write.end <= from || to <= write.begin) {
rest.emplace_back(from, to);
continue;
}
covered = true;
if (from < write.begin) rest.emplace_back(from, write.begin);
if (write.end < to) rest.emplace_back(write.end, to);
}
unwritten = std::move(rest);
if (!covered) continue;
if (write.value >= 0) see(write.value);
else landed.unknown = true;
}
for (const auto& [from, to] : unwritten) {
if (landed.mixed) break;
const auto* keys = reinterpret_cast<const std::uint8_t*>(from);
if (AllKeysEqual(keys + 1, static_cast<std::size_t>(to - from) - 1, keys[0])) see(keys[0]);
else landed.mixed = true;
}
return landed;
}

std::optional<DccKeys> PendingStoreKeys(Recorder& recorder, std::uint64_t begin, std::size_t count) {
const auto end = begin + count;
std::uint64_t note = 0;
Expand All @@ -308,8 +350,16 @@ std::optional<DccKeys> PendingStoreKeys(Recorder& recorder, std::uint64_t begin,
break;
}
}
if (note == 0 || AnyShadowedOverlaps(begin, count) || recorder.NewestWriteNote(begin, count) != note) return std::nullopt;
return keys;
if (AnyShadowedOverlaps(begin, count)) {
PublishShadow(begin, count, PublishScope::Whole, PublishReason::Keys);
if (AnyShadowedOverlaps(begin, count)) return std::nullopt;
}
if (note != 0 && recorder.NewestWriteNote(begin, count) == note) return keys;
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(begin), count)) return std::nullopt;
const auto landed = KeysAfterPendingWrites(recorder, begin, count);
if (landed.mixed) return DccKeys::Mixed;
if (landed.unknown || landed.value < 0) return std::nullopt;
return ByteKeys(static_cast<std::uint8_t>(landed.value));
}

// Whether the keys already read as uncompressed: scans the same bytes as ReadDccKeys, so it counts
Expand Down Expand Up @@ -465,12 +515,14 @@ DccKeys readDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized
return uniform ? ByteKeys(first) : DccKeys::Mixed;
}

DccKeys currentDccKeys(std::uint64_t metaAddress, std::size_t count, bool& memoized);

DccKeys textureClearKeys(const GuestTextureResource& resource, std::uint64_t guestBytes, bool& memoized) {
memoized = false;
if (resource.dccAddress == 0) return DccKeys::Uncompressed;
DccKeys keys;
if (const auto known = WaitForKeyWriters(resource, guestBytes)) keys = *known;
else keys = readDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized);
else keys = currentDccKeys(resource.dccAddress, DccKeyBytes(guestBytes), memoized);
if (keys == DccKeys::Uncompressed || (keys == DccKeys::ClearSingle && SingleKeysReadable(resource))) return keys;
if (IsConvertedTextureFormat(resource.format) && (keys == DccKeys::Clear0001 || keys == DccKeys::Clear1110)) throw std::runtime_error(std::string("AGC graphics: DCC clear code ") + DccKeysName(keys) + " of converted texture format " + std::to_string(resource.format) + " is not implemented");
std::byte probe[16]{};
Expand Down Expand Up @@ -695,38 +747,8 @@ DccKeys EncodedKeys(const GuestTextureResource& resource, DccKeys keys) {
bool PendingFillsLeaveUncompressed(const Recorder& recorder, const GuestTextureResource& resource, std::size_t count) {
const auto begin = resource.dccAddress;
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(begin), count) || AnyShadowedOverlaps(begin, count)) return false;
auto writes = recorder.PendingWritesOver(begin, count);
std::sort(writes.begin(), writes.end(), [](const Recorder::PendingWrite& a, const Recorder::PendingWrite& b) { return a.note > b.note; });
std::vector<std::pair<std::uint64_t, std::uint64_t>> unwritten{{begin, begin + count}};
int value = -1;
bool mixed = false;
const auto see = [&](int key) {
if (value < 0) value = key;
else if (value != key) mixed = true;
};
for (const auto& write : writes) {
if (mixed || unwritten.empty()) break;
std::vector<std::pair<std::uint64_t, std::uint64_t>> rest;
bool covered = false;
for (const auto& [from, to] : unwritten) {
if (write.end <= from || to <= write.begin) {
rest.emplace_back(from, to);
continue;
}
covered = true;
if (from < write.begin) rest.emplace_back(from, write.begin);
if (write.end < to) rest.emplace_back(write.end, to);
}
unwritten = std::move(rest);
if (covered && write.value >= 0) see(write.value);
}
for (const auto& [from, to] : unwritten) {
if (mixed) break;
const auto* keys = reinterpret_cast<const std::uint8_t*>(from);
if (AllKeysEqual(keys + 1, static_cast<std::size_t>(to - from) - 1, keys[0])) see(keys[0]);
else mixed = true;
}
return mixed || (value >= 0 && EncodedKeys(resource, ByteKeys(static_cast<std::uint8_t>(value))) == DccKeys::Uncompressed);
const auto landed = KeysAfterPendingWrites(recorder, begin, count);
return landed.mixed || (landed.value >= 0 && EncodedKeys(resource, ByteKeys(static_cast<std::uint8_t>(landed.value))) == DccKeys::Uncompressed);
}

}
Expand Down
137 changes: 137 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4613,7 +4613,31 @@ void pendingKeyStoreTests(const Device& device, Recorder& recorder) {
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Clear0001 && recorder.PendingWriteOverlaps(address, keyCount), "the keys of a pending fill did not read as its keys without a wait");
recorder.NotePendingWrite(address, 2 * keyCount);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Uncompressed && !recorder.PendingWriteOverlaps(address, keyCount), "a later wider writer over a pending fill was not waited for");
recorder.NotePendingFill(address, keyCount / 2, 0x40);
recorder.NotePendingFill(address + keyCount / 2, keyCount / 2, 0x40);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Clear0001 && recorder.PendingWriteOverlaps(address, keyCount), "the keys of pending 0001 fills over both halves did not read as 0001 without a wait");
recorder.NotePendingWrite(address + keyCount / 2, 16);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Uncompressed && !recorder.PendingWriteOverlaps(address, keyCount), "a later writer of unknown bytes over pending fills was not waited for");
recorder.NotePendingFill(address, keyCount / 2, 0x00);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Mixed && recorder.PendingWriteOverlaps(address, keyCount), "a pending 0000 fill over half of uncompressed keys did not read as mixed without a wait");
recorder.Sync();
#ifdef _WIN32
auto* edgeBlock = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, 2 * bytes, MEM_RESERVE, PAGE_NOACCESS));
Require(edgeBlock != nullptr && VirtualAlloc(edgeBlock, bytes, MEM_COMMIT, PAGE_READWRITE) != nullptr, "cannot reserve the key block past committed memory");
#else
void* edgeMapping = mmap(nullptr, 2 * bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(edgeMapping != MAP_FAILED && mprotect(edgeMapping, bytes, PROT_READ | PROT_WRITE) == 0, "cannot reserve the key block past committed memory");
auto* edgeBlock = static_cast<std::uint8_t*>(edgeMapping);
#endif
const auto edge = reinterpret_cast<std::uint64_t>(edgeBlock) + bytes - keyCount / 2;
Require(!AgcDriver::GuestMemory::Accessible(reinterpret_cast<const void*>(edge), keyCount), "keys past the end of committed memory read as accessible");
recorder.NotePendingFill(edge, keyCount / 2, 0x40);
Require(CurrentDccKeys(edge, surfaceBytes) == DccKeys::Unreadable && !recorder.PendingWriteOverlaps(edge, keyCount), "keys running past the end of committed memory, with a pending fill over their committed half, were not read as unreadable after a wait");
#ifdef _WIN32
VirtualFree(edgeBlock, 0, MEM_RELEASE);
#else
munmap(edgeBlock, 2 * bytes);
#endif
constexpr std::uint64_t displayBytes = 15u * 9u * 65536u;
constexpr std::size_t consoleKeys = 49152;
const auto displayKeys = DccKeyCount(TextureTileMode::kR64KBX, 4, 1920, 1080, displayBytes);
Expand All @@ -4628,6 +4652,101 @@ void pendingKeyStoreTests(const Device& device, Recorder& recorder) {
recorder.Sync();
}

bool shadowedKeyStoreTests(const Device& device, Recorder& recorder) {
using namespace AgcDriver::GuestMemory;
const auto& context = device.GetContext();
if (!UnitShadowEnabled() || context.hostImportAlignment == 0) {
std::cout << "unit shadows or host imports unavailable: keys under a unit shadow not tested\n";
return false;
}
constexpr std::uint64_t unit = 65536;
constexpr std::size_t bytes = 2 * unit;
constexpr std::size_t surfaceBytes = 65536;
constexpr std::size_t keyCount = surfaceBytes / 256;
#ifdef _WIN32
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, unit);
Require(block != nullptr, "cannot reserve the shadowed key block");
const auto section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, static_cast<DWORD>(bytes), nullptr);
Require(section != nullptr, "cannot create the shadowed key block");
GuestArena::GuestArenaMap_nid_postfix(block, bytes, section, 0, PAGE_READWRITE);
CloseHandle(section);
#else
void* block = AllocateWatched(bytes, unit);
Require(block != nullptr, "cannot allocate the shadowed key block");
#endif
struct Release {
void* block;
~Release() { ReleaseWatched(block, bytes); }
} release{block};
auto* keys = static_cast<std::uint8_t*>(block);
std::memset(block, 0xff, bytes);
const auto address = reinterpret_cast<std::uint64_t>(block);
{
GuestAllocations::Mutation mutation;
mutation.Add(block, bytes, true, true, true);
}
struct Unregister {
const Context& context;
void* block;
std::uint64_t address;
~Unregister() {
{
GuestAllocations::Mutation mutation;
mutation.Remove(block);
}
HostImportFor(context, address, bytes);
}
} unregister{context, block, address};
const auto* import = HostImportFor(context, address, bytes);
if (import == nullptr || !Watched(address, bytes)) {
std::cout << "the shadowed key block is not imported and watched: keys under a unit shadow not tested\n";
return false;
}
recorder.Sync();
CollectWritesUncached(address, bytes);
const auto copyBuffer = context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer");
const auto shadow = [&](std::uint8_t value) {
auto destination = ShadowDestinationFor(context, *import, address, address + unit);
Require(destination.has_value() && destination->seedUnits.empty(), "the key unit has no unit shadow destination");
auto pattern = std::make_shared<Buffer>(context, static_cast<std::size_t>(unit), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
std::memset(pattern->Bytes().data(), value, pattern->Bytes().size());
const auto commands = recorder.Commands();
recorder.Keep(pattern);
recorder.Keep(destination->slab);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
const VkBufferCopy piece{0, destination->offset, unit};
copyBuffer(commands, pattern->Handle(), destination->buffer, 1, &piece);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
recorder.MarkCovered(VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
const ShadowedRange range{address, address + unit, destination->slab};
MarkShadowed(*import, std::span(&range, 1), TrackerGeneration());
Require(AnyShadowedOverlaps(address, keyCount), "the key unit is not shadowed");
};
recorder.NotePendingFill(address, unit, 0x00);
MarkWritten(address, unit);
shadow(0x40);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Clear0001 && !recorder.PendingWriteOverlaps(address, keyCount), "the keys under a fresh unit shadow were not read from its published bytes after a wait");
Require(keys[0] == 0x40 && keys[unit - 1] == 0x40 && keys[unit] == 0xff && !AnyShadowedOverlaps(address, unit), "the fresh unit's publish did not land");
recorder.Sync();
shadow(0x22);
recorder.NotePendingFill(address, unit, 0xc0);
MarkWritten(address, unit);
Require(CurrentDccKeys(address, surfaceBytes) == DccKeys::Clear1111 && recorder.PendingWriteOverlaps(address, keyCount), "the keys of a pending fill over a stale unit shadow were not read as the fill's keys without a wait");
Require(!AnyShadowedOverlaps(address, unit), "the stale unit still counts as shadowed after the key read");
recorder.Sync();
GuestTextureResource resource{};
resource.baseAddress = address + unit;
resource.format = 56;
resource.dccAddress = address;
shadow(0x22);
recorder.NotePendingFill(address, unit, 0xc0);
MarkWritten(address, unit);
Require(!WaitForKeyWriters(resource, surfaceBytes).has_value() && recorder.PendingWriteOverlaps(address, keyCount), "the key writers over a stale unit shadow were waited for");
recorder.Sync();
Require(keys[0] == 0x40 && keys[unit - 1] == 0x40, "a stale unit's slab bytes were published");
return true;
}

void sampleDumpTests(const Device& device, Recorder& recorder) {
const auto& context = device.GetContext();
if (context.hostImportAlignment == 0 || !context.bufferDeviceAddress) {
Expand Down Expand Up @@ -6604,6 +6723,23 @@ int main(int argc, char** argv) {
std::cout << "Color metadata pass tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--pending-key-stores-only") {
if (device.GetContext().hostImportAlignment == 0) {
std::cout << "skipped, host imports are unavailable\n";
return 77;
}
pendingKeyStoreTests(device, recorder);
std::cout << "Pending DCC key store tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--shadowed-key-stores-only") {
if (!shadowedKeyStoreTests(device, recorder)) {
std::cout << "skipped, unit shadows over a watched host import are unavailable\n";
return 77;
}
std::cout << "DCC keys under unit shadows tests passed\n";
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--draw-inputs-in-place-only") {
drawInputInPlaceTests(device, recorder);
std::cout << "In-place draw input tests passed\n";
Expand Down Expand Up @@ -6696,6 +6832,7 @@ int main(int argc, char** argv) {
metadataPassTests(device, recorder);
cmaskPassTests(device, recorder);
pendingKeyStoreTests(device, recorder);
shadowedKeyStoreTests(device, recorder);
sampleDumpTests(device, recorder);
copiedDirectOrderingTests(device, recorder);
gpu.unlock();
Expand Down
Loading
Loading