perf(agc): cache a draw key the second time it is seen
A draw whose key has no entry captured its stages, built a variant per stage (a copy of every captured region) and inserted a draw entry. A key that never comes back paid for the variants and the entry for nothing, and its entry pushed a live one out of the cache. Draws whose user data changes every frame are such keys. The driver now remembers the keys it has seen once, in a table of four-way buckets holding four times the cache's size in keys. A key gets its entry at its second sight; a key that already has an entry still takes new variants at once. A full bucket replaces its oldest key, so keys that share a bucket do not push each other out until more than four of them come between two sights of one. Test: agc_driver_draw_cache_reuse draws three times. The first two draws find no entry, one entry is inserted, and the third draw hits it. A sighting table of one bucket keeps two keys drawn in turn and replaces its oldest key when full.
This commit is contained in:
parent
60fb618708
commit
0adbdcf45c
|
|
@ -75,6 +75,19 @@ struct DrawEntryCounters {
|
|||
std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
class DrawKeySightings {
|
||||
public:
|
||||
static constexpr std::size_t Ways = 4;
|
||||
explicit DrawKeySightings(std::size_t keys);
|
||||
bool SeenBefore(std::uint64_t key);
|
||||
|
||||
private:
|
||||
std::vector<std::uint64_t> keys;
|
||||
std::vector<std::uint8_t> filled;
|
||||
std::vector<std::uint8_t> next;
|
||||
std::size_t bucketMask = 0;
|
||||
};
|
||||
|
||||
enum class DrawVerdict { Drawn, Nothing, Rejected };
|
||||
|
||||
struct StageCapture {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ private:
|
|||
static std::size_t drawCacheEntries();
|
||||
void accountDrawVariant(const DispatchVariant& variant, bool added);
|
||||
void insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::shared_ptr<const DrawDecode> decode);
|
||||
bool admitDrawKey(std::uint64_t key);
|
||||
void eraseDrawEntry(std::uint64_t key, const std::shared_ptr<DrawEntry>& expected);
|
||||
std::shared_ptr<const DrawRecipe> findDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages);
|
||||
void attachDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages, std::shared_ptr<const DrawRecipe> recipe);
|
||||
|
|
@ -310,6 +311,7 @@ private:
|
|||
std::list<std::uint64_t> drawOrder;
|
||||
std::mutex drawCacheMutex;
|
||||
std::uint64_t drawCacheHits = 0, drawCacheEvictions = 0, drawCacheVariants = 0, drawCacheVariantBytes = 0;
|
||||
std::optional<DrawKeySightings> drawKeySightings;
|
||||
|
||||
DrawEntryCounters drawEntryCounters;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace AgcDriver::DriverDetail {
|
||||
|
|
@ -56,6 +57,36 @@ void Driver::accountDrawVariant(const DispatchVariant& variant, bool added) {
|
|||
}
|
||||
}
|
||||
|
||||
DrawKeySightings::DrawKeySightings(std::size_t keyCount) {
|
||||
Graphics::Require(keyCount >= Ways, "draw key sightings need at least one bucket");
|
||||
const auto buckets = std::bit_ceil(keyCount / Ways);
|
||||
keys.assign(buckets * Ways, 0);
|
||||
filled.assign(buckets, 0);
|
||||
next.assign(buckets, 0);
|
||||
bucketMask = buckets - 1;
|
||||
}
|
||||
|
||||
bool DrawKeySightings::SeenBefore(std::uint64_t key) {
|
||||
auto mixed = key;
|
||||
mixed = (mixed ^ (mixed >> 30u)) * 0xbf58476d1ce4e5b9ull;
|
||||
mixed = (mixed ^ (mixed >> 27u)) * 0x94d049bb133111ebull;
|
||||
mixed ^= mixed >> 31u;
|
||||
const auto bucket = static_cast<std::size_t>(mixed) & bucketMask;
|
||||
auto* ways = keys.data() + bucket * Ways;
|
||||
if (std::find(ways, ways + filled[bucket], key) != ways + filled[bucket]) return true;
|
||||
ways[next[bucket]] = key;
|
||||
next[bucket] = static_cast<std::uint8_t>((next[bucket] + 1u) % Ways);
|
||||
if (filled[bucket] < Ways) ++filled[bucket];
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Driver::admitDrawKey(std::uint64_t key) {
|
||||
std::lock_guard cacheLock(drawCacheMutex);
|
||||
if (drawCache.contains(key)) return true;
|
||||
if (!drawKeySightings) drawKeySightings.emplace(drawCacheEntries() * DrawKeySightings::Ways);
|
||||
return drawKeySightings->SeenBefore(key);
|
||||
}
|
||||
|
||||
void Driver::insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::shared_ptr<const DrawDecode> decode) {
|
||||
std::lock_guard cacheLock(drawCacheMutex);
|
||||
auto& counters = drawEntryCounters;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, st
|
|||
}
|
||||
|
||||
void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional<Graphics::IndirectDrawPath>& indirectCpu, const std::vector<DrawProgram>& programs, const std::vector<StageCapture>& stageCaptures, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, const std::vector<std::vector<Graphics::DecodeRead>>& decodeReads, bool verifyHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr<const DrawDecode>& decode, DrawPhaseTiming& phaseTiming) {
|
||||
if (useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu)) {
|
||||
if (useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu) && (verifyHit || admitDrawKey(drawKey))) {
|
||||
phaseTiming.Phase(DrawRowVectors);
|
||||
std::uint64_t unstable = 0, mismatches = 0;
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ namespace {
|
|||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using AgcDriver::DriverDetail::DrawMiss;
|
||||
using AgcDriver::DriverDetail::DrawKeySightings;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> VertexCode{0x7e000208u, 0x7e020280u, 0x7e040280u, 0x7e0602f2u, 0xf80008cfu, 0x03020100u, 0xbf810000u, 0xbf810000u};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 8> PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u};
|
||||
|
|
@ -48,7 +49,7 @@ void Append(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::ini
|
|||
commands.insert(commands.end(), payload);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> DrawTwice() {
|
||||
std::vector<std::uint32_t> DrawThrice() {
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(ColorMemory.data());
|
||||
const auto vertex = reinterpret_cast<std::uintptr_t>(VertexCode.data());
|
||||
const auto pixel = reinterpret_cast<std::uintptr_t>(PixelCode.data());
|
||||
|
|
@ -83,28 +84,41 @@ std::vector<std::uint32_t> DrawTwice() {
|
|||
for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value});
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
return commands;
|
||||
}
|
||||
|
||||
void SecondDrawHitsTheCache(bool libraries) {
|
||||
void ThirdDrawHitsTheCache(bool libraries) {
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1);
|
||||
auto commands = DrawTwice();
|
||||
auto commands = DrawThrice();
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
const auto counters = AgcDriver::DriverDetail::Driver::Get().DrawCacheCounters();
|
||||
const auto mode = std::string(libraries ? "with" : "without") + " graphics pipeline libraries";
|
||||
const auto layout = counters.misses[static_cast<std::size_t>(DrawMiss::Layout)];
|
||||
Require(counters.lookups == 2 && counters.absent == 1, mode + ": the two draws made " + std::to_string(counters.lookups) + " draw-cache lookups (" + std::to_string(counters.absent) + " without an entry), expected 2 lookups with 1 absent");
|
||||
Require(counters.lookups == 3 && counters.absent == 2, mode + ": the three draws made " + std::to_string(counters.lookups) + " draw-cache lookups (" + std::to_string(counters.absent) + " without an entry), expected 3 lookups with 2 absent");
|
||||
Require(counters.inserts == 1, mode + ": the three draws inserted " + std::to_string(counters.inserts) + " draw entries, expected 1: a key is cached the second time it is seen");
|
||||
Require(layout == 0, mode + ": the repeated draw missed the draw cache on its push offset (" + std::to_string(layout) + " layout misses)");
|
||||
Require(counters.hits == 1, mode + ": the repeated draw did not reuse the cached draw (" + std::to_string(counters.hits) + " hits)");
|
||||
}
|
||||
|
||||
void SightingsKeepCollidingKeys() {
|
||||
DrawKeySightings sightings(DrawKeySightings::Ways);
|
||||
Require(!sightings.SeenBefore(0) && sightings.SeenBefore(0), "draw key 0 was not admitted at its second sight");
|
||||
Require(!sightings.SeenBefore(11) && !sightings.SeenBefore(12), "an unseen draw key was admitted");
|
||||
Require(sightings.SeenBefore(11) && sightings.SeenBefore(12) && sightings.SeenBefore(11), "two keys in one bucket, drawn in turn, were not admitted at their second sight");
|
||||
Require(!sightings.SeenBefore(13) && !sightings.SeenBefore(14), "an unseen draw key was admitted");
|
||||
Require(!sightings.SeenBefore(0), "the oldest key of a full bucket was kept over the newest");
|
||||
Require(sightings.SeenBefore(12) && sightings.SeenBefore(13) && sightings.SeenBefore(14), "a full bucket lost a key newer than the one it replaced");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
SightingsKeepCollidingKeys();
|
||||
bool libraries = false;
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
|
|
@ -112,7 +126,7 @@ int main() {
|
|||
libraries = device->GraphicsPipelineLibraries();
|
||||
}
|
||||
if (!libraries && std::getenv("APS5_NO_GPL") == nullptr) return VulkanTestSkipped;
|
||||
SecondDrawHitsTheCache(libraries);
|
||||
ThirdDrawHitsTheCache(libraries);
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::cout << "A repeated draw reused the cached draw " << (libraries ? "with" : "without") << " graphics pipeline libraries\n";
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue