mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 17:13:13 -05:00
Write stamps are per 64 KiB block, so a write-back of one surface stamps the whole block it shares with the surface packed right after it. A pending image of that neighbour read the stamp as a store over its own bytes: its write-back kept the guest bytes of the shared block in place of its results and its next refresh re-uploaded them, stale. PPSA21564's half-resolution fog texture (960x540 RGBA16F, linear, starting 0xb800 bytes before a block end right after another 960x540 surface) lost its first 6 rows that way, which the fog composite upsampled into an orange band across the top 12 rows of the scene in one of its cinematics. The tracker now remembers the byte ranges of the driver's own stores (MarkWritten) in the blocks they cover only in part, and StoredOver says whether a store reached a given range since a generation (a CPU store still counts for its whole block). The write-back keep rule and the refresh drop rule ask it for the blocks a surface covers only in part.
109 lines
5.0 KiB
C++
109 lines
5.0 KiB
C++
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
|
#include "prx/libc/include/GuestArena.hpp"
|
|
#include "prx/libc/include/GuestWriteWatch.hpp"
|
|
#ifdef _WIN32
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#endif
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
using namespace AgcDriver::GuestMemory;
|
|
|
|
constexpr std::size_t Block = 65536;
|
|
|
|
void Require(bool condition, const std::string& message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
void* AllocateWatched(std::size_t bytes) {
|
|
#ifdef _WIN32
|
|
void* block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block);
|
|
GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
|
|
#else
|
|
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (raw == MAP_FAILED) throw std::runtime_error("cannot map the watched block");
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
|
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
|
|
if (aligned != begin) munmap(raw, aligned - begin);
|
|
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
|
|
void* block = reinterpret_cast<void*>(aligned);
|
|
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
|
|
#endif
|
|
Require(Watched(reinterpret_cast<std::uint64_t>(block), bytes), "the test block is not watched");
|
|
return block;
|
|
}
|
|
|
|
void CheckSharedBlock() {
|
|
void* memory = AllocateWatched(3 * Block);
|
|
const auto base = reinterpret_cast<std::uint64_t>(memory);
|
|
const auto boundary = base + 2 * Block - 0xb800;
|
|
const auto firstBegin = base;
|
|
const auto secondEnd = base + 3 * Block;
|
|
const auto sharedFirst = boundary - (base + Block);
|
|
const auto sharedSecond = base + 2 * Block - boundary;
|
|
std::memset(memory, 0x11, 3 * Block);
|
|
|
|
const auto synced = CollectWrites(base, 3 * Block);
|
|
Require(synced != 0, "the test block is not collected");
|
|
|
|
Require(MarkWritten(firstBegin, static_cast<std::size_t>(boundary - firstBegin)) != 0, "a driver store into watched memory is not stamped");
|
|
std::array<std::uint64_t, 2> generations{synced, synced};
|
|
std::array<std::uint8_t, 2> changed{};
|
|
Require(ChangedBlocks(base + Block, 2 * Block, generations, changed) && changed[0] == BlockWritten && changed[1] == BlockUnchanged, "the shared block does not read as written");
|
|
Require(!StoredOver(boundary, static_cast<std::size_t>(sharedSecond), synced), "a store of the neighbour's bytes counts for the second surface's part of the shared block");
|
|
Require(StoredOver(base + Block, static_cast<std::size_t>(sharedFirst), synced), "the first surface's own store is not seen over its part of the shared block");
|
|
Require(!StoredOver(boundary, static_cast<std::size_t>(secondEnd - boundary), synced), "the second surface reads as stored over");
|
|
|
|
const auto touched = MarkWritten(boundary + 0x100, 4);
|
|
Require(StoredOver(boundary, static_cast<std::size_t>(sharedSecond), synced), "a store over the second surface's bytes is not seen");
|
|
Require(!StoredOver(boundary, static_cast<std::size_t>(sharedSecond), touched), "a store older than the generation is seen");
|
|
|
|
const auto whole = TrackerGeneration();
|
|
MarkWritten(base + Block, Block);
|
|
Require(StoredOver(boundary, static_cast<std::size_t>(sharedSecond), whole), "a store over the whole block is not seen");
|
|
const auto afterWhole = TrackerGeneration();
|
|
MarkWritten(base + Block, static_cast<std::size_t>(sharedFirst));
|
|
Require(!StoredOver(boundary, static_cast<std::size_t>(sharedSecond), afterWhole), "a partial store after a whole-block store is taken for the whole block");
|
|
|
|
const auto many = TrackerGeneration();
|
|
for (int store = 0; store < 6; ++store) MarkWritten(base + Block + static_cast<std::uint64_t>(store) * 64, 4);
|
|
Require(StoredOver(boundary, static_cast<std::size_t>(sharedSecond), many), "forgotten driver stores are taken as missing the range");
|
|
|
|
const auto beforeCpu = CollectWrites(base, 3 * Block);
|
|
static_cast<volatile std::uint8_t*>(memory)[Block + 8] = 0x22;
|
|
CollectWritesUncached(base, 3 * Block);
|
|
Require(StoredOver(boundary, static_cast<std::size_t>(sharedSecond), beforeCpu), "a CPU store in the shared block is not seen");
|
|
|
|
std::array<std::uint8_t, 64> unwatched{};
|
|
const auto outside = reinterpret_cast<std::uint64_t>(unwatched.data());
|
|
if (!Watched(outside, unwatched.size())) Require(StoredOver(outside, unwatched.size(), TrackerGeneration()), "an unwatched range reads as not stored over");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
if (!WriteWatched()) {
|
|
std::cout << "no write watching: skipped\n";
|
|
return 77;
|
|
}
|
|
CheckSharedBlock();
|
|
} catch (const std::exception& error) {
|
|
std::cerr << "write tracking test failed: " << error.what() << "\n";
|
|
return 1;
|
|
}
|
|
std::cout << "write tracking tests passed\n";
|
|
return 0;
|
|
}
|