mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
feat(agc): count which key component differs on resource-content cache misses
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dacd78525e
commit
5cf0aa48d9
4 files changed
+298
-3
No files matched your search
@@ -348,6 +348,29 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_driver_key_miss_tests EXCLUDE_FROM_ALL tests/KeyMiss.cpp)
|
||||
target_include_directories(agc_driver_key_miss_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/core/shader/recompiler
|
||||
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(agc_driver_key_miss_tests PRIVATE
|
||||
libSceAgcDriver
|
||||
libc
|
||||
libkernel
|
||||
)
|
||||
set_target_properties(agc_driver_key_miss_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_key_miss_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_driver_key_miss_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_driver_key_miss COMMAND agc_driver_key_miss_tests)
|
||||
set_tests_properties(agc_driver_key_miss PROPERTIES TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_key_miss PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL
|
||||
tests/Submit.cpp
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -106,6 +107,10 @@ private:
|
||||
Stats stats;
|
||||
};
|
||||
|
||||
enum class ContentKeyComponent : std::size_t { Layout, BufferWord0, BufferWord1, BufferWord2, BufferWord3, Image, Sampler, ShaderData, Other, DrawTarget, Count };
|
||||
|
||||
using ContentKeyDiff = std::array<bool, static_cast<std::size_t>(ContentKeyComponent::Count)>;
|
||||
|
||||
class ShaderResources {
|
||||
public:
|
||||
ShaderResources(const Context& context, const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment, const ColorTarget& target, std::uint64_t indexAddress, std::size_t indexBytes);
|
||||
@@ -195,6 +200,7 @@ public:
|
||||
// (their count and size remain): a compute template then serves dispatches whose constants
|
||||
// differ, and the hit refreshes its data buffers with the dispatch's words (RefreshData).
|
||||
static std::vector<std::uint32_t> ContentKey(const CompiledShader& shader, bool dataWords = true, bool movableBuffers = false);
|
||||
static bool DiffContentKeys(std::span<const std::uint32_t> previous, std::span<const std::uint32_t> key, bool draw, ContentKeyDiff& diff);
|
||||
// Records the shader's ShaderData and FlattenedSrt words into this object's data buffers
|
||||
// (vkCmdUpdateBuffer, a transfer write the caller's pre-dispatch barrier makes visible; a
|
||||
// buffer already holding the words is left alone). Returns whether anything was recorded. With
|
||||
|
||||
@@ -1810,6 +1810,88 @@ std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& sha
|
||||
return key;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using Component = ContentKeyComponent;
|
||||
|
||||
bool LabelShaderKey(std::span<const std::uint32_t> key, std::size_t at, std::size_t end, bool movable, std::vector<std::uint8_t>& labels) {
|
||||
const auto label = [&](std::size_t first, std::size_t count, Component component) {
|
||||
if (first + count > end) return false;
|
||||
std::fill(labels.begin() + static_cast<std::ptrdiff_t>(first), labels.begin() + static_cast<std::ptrdiff_t>(first + count), static_cast<std::uint8_t>(component));
|
||||
return true;
|
||||
};
|
||||
if (!label(at, 5, Component::Layout)) return false;
|
||||
const bool dataWords = key[at] != 0;
|
||||
const auto bindings = key[at + 4];
|
||||
at += 5;
|
||||
for (std::uint32_t binding = 0; binding < bindings; ++binding) {
|
||||
if (!label(at, 8, Component::Layout)) return false;
|
||||
const auto role = static_cast<ShaderRecompiler::DescriptorRole>(key[at + 1]);
|
||||
const auto size = key[at + 7];
|
||||
at += 8;
|
||||
const auto words = DataRole(role) && !(dataWords && !movable) ? 0u : size;
|
||||
if (at + words > end) return false;
|
||||
for (std::uint32_t i = 0; i < words; ++i) {
|
||||
auto component = Component::Other;
|
||||
if (role == ShaderRecompiler::DescriptorRole::GuestBuffers) component = static_cast<Component>(static_cast<std::size_t>(Component::BufferWord0) + i % 4u);
|
||||
else if (role == ShaderRecompiler::DescriptorRole::GuestImages) component = Component::Image;
|
||||
else if (role == ShaderRecompiler::DescriptorRole::GuestSamplers) component = Component::Sampler;
|
||||
else if (DataRole(role)) component = Component::ShaderData;
|
||||
labels[at + i] = static_cast<std::uint8_t>(component);
|
||||
}
|
||||
at += words;
|
||||
for (int flags = 0; flags < 9; ++flags) {
|
||||
if (at >= end) return false;
|
||||
const auto count = 1 + (static_cast<std::size_t>(key[at]) + 31) / 32;
|
||||
if (!label(at, count, Component::Other)) return false;
|
||||
at += count;
|
||||
}
|
||||
if (at >= end) return false;
|
||||
const auto samplers = static_cast<std::size_t>(key[at]);
|
||||
if (!label(at, 1 + samplers, Component::Sampler)) return false;
|
||||
at += 1 + samplers;
|
||||
}
|
||||
return at == end;
|
||||
}
|
||||
|
||||
bool LabelContentKey(std::span<const std::uint32_t> key, bool draw, bool movable, std::vector<std::uint8_t>& labels) {
|
||||
labels.assign(key.size(), static_cast<std::uint8_t>(Component::Layout));
|
||||
if (!draw) return LabelShaderKey(key, 0, key.size(), movable, labels);
|
||||
if (key.size() < 4) return false;
|
||||
std::fill(labels.begin() + 1, labels.begin() + 3, static_cast<std::uint8_t>(Component::Other));
|
||||
std::size_t at = 4;
|
||||
for (std::uint32_t stage = 0; stage < key[3]; ++stage) {
|
||||
if (at >= key.size()) return false;
|
||||
const auto size = static_cast<std::size_t>(key[at]);
|
||||
++at;
|
||||
if (at + size > key.size() || !LabelShaderKey(key, at, at + size, movable, labels)) return false;
|
||||
at += size;
|
||||
}
|
||||
if (at >= key.size()) return false;
|
||||
for (auto tail = at + 1; tail < key.size(); ++tail) labels[tail] = static_cast<std::uint8_t>(Component::DrawTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ShaderResources::DiffContentKeys(std::span<const std::uint32_t> previous, std::span<const std::uint32_t> key, bool draw, ContentKeyDiff& diff) {
|
||||
diff = {};
|
||||
std::vector<std::uint8_t> labels;
|
||||
bool labelled = false;
|
||||
for (const bool movable : {draw, false}) {
|
||||
labelled = LabelContentKey(key, draw, movable, labels);
|
||||
if (labelled) break;
|
||||
}
|
||||
if (!labelled || previous.size() != key.size()) {
|
||||
diff[static_cast<std::size_t>(Component::Layout)] = true;
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < key.size(); ++i) {
|
||||
if (previous[i] != key[i]) diff[labels[i]] = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchesContentKey(const CompiledShader& shader, std::span<const std::uint32_t> key) {
|
||||
std::size_t cursor = 0;
|
||||
bool equal = true;
|
||||
@@ -2503,6 +2585,14 @@ namespace {
|
||||
// seen for the same shader variant(s) and the first differing word is charged to its binding's role
|
||||
// (and to its index within the V# or T# element), printed as [rescache] every 10 s: it says whether
|
||||
// V# bases of ring allocations, SRT words or image descriptors churn.
|
||||
struct KeyMissWindow {
|
||||
std::uint64_t compared = 0;
|
||||
std::uint64_t noCandidate = 0;
|
||||
std::uint64_t unlabelled = 0;
|
||||
std::uint64_t identical = 0;
|
||||
std::array<std::uint64_t, static_cast<std::size_t>(ContentKeyComponent::Count)> differing{};
|
||||
};
|
||||
|
||||
struct ChurnCounts {
|
||||
std::array<std::uint64_t, 8> roles{};
|
||||
std::array<std::uint64_t, 4> bufferWords{};
|
||||
@@ -2511,6 +2601,7 @@ struct ChurnCounts {
|
||||
std::uint64_t sameKey = 0;
|
||||
std::uint64_t layout = 0;
|
||||
std::uint64_t drawWords = 0;
|
||||
KeyMissWindow window;
|
||||
};
|
||||
|
||||
// Dispatch keys and draw keys are counted apart: what churns a draw's key decides the draw steps.
|
||||
@@ -2519,9 +2610,21 @@ struct ChurnProfile {
|
||||
std::unordered_map<std::uint64_t, ResourceCache::Key> lastByVariant;
|
||||
ChurnCounts dispatch;
|
||||
ChurnCounts draws;
|
||||
std::unordered_map<std::uint64_t, std::uint32_t> tracedByVariant;
|
||||
std::size_t tracedLines = 0;
|
||||
std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
constexpr std::size_t MaxTracedLines = 256;
|
||||
constexpr std::uint32_t MaxTracedLinesPerVariant = 4;
|
||||
|
||||
const char* componentName(std::size_t component) {
|
||||
constexpr std::array<const char*, static_cast<std::size_t>(ContentKeyComponent::Count)> names{"layout", "V# word 0", "V# word 1", "V# word 2", "V# word 3", "image", "sampler", "shader-data", "other", "draw target/index"};
|
||||
return names[component];
|
||||
}
|
||||
|
||||
void traceMiss(ChurnProfile& churn, bool draw, std::uint64_t variants, const ContentKeyDiff* parts);
|
||||
|
||||
ChurnProfile& Churn() {
|
||||
static ChurnProfile profile;
|
||||
return profile;
|
||||
@@ -2584,9 +2687,28 @@ std::shared_ptr<ShaderResources> ResourceCache::Find(const Key& key) {
|
||||
return found->second->second;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void traceMiss(ChurnProfile& churn, bool draw, std::uint64_t variants, const ContentKeyDiff* parts) {
|
||||
if (churn.tracedLines >= MaxTracedLines || ++churn.tracedByVariant[variants] > MaxTracedLinesPerVariant) return;
|
||||
++churn.tracedLines;
|
||||
std::string changed = parts == nullptr ? "first-seen" : "";
|
||||
for (std::size_t i = 0; parts != nullptr && i < parts->size(); ++i) {
|
||||
if (!(*parts)[i]) continue;
|
||||
if (!changed.empty()) changed += ", ";
|
||||
changed += componentName(i);
|
||||
}
|
||||
if (changed.empty()) changed = "no-component-change";
|
||||
const auto hashed = (variants ^ 0x9e3779b97f4a7c15ull) * 0xff51afd7ed558ccdull;
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[keymiss] resource-content %s shader set %016llx: %s\n", draw ? "draw" : "dispatch", static_cast<unsigned long long>(hashed ^ (hashed >> 33u)), changed.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ResourceCache::noteMiss(const Key& key) {
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
if (!profile || key.size() < 5) return;
|
||||
static const bool trace = std::getenv("APS5_TRACE_RESOURCE_KEY_MISSES") != nullptr;
|
||||
if ((!profile && !trace) || key.size() < 5) return;
|
||||
// The variant(s) the key belongs to: a compute key starts with the data-words flag, the stage
|
||||
// and its variant, a draw key with a marker, the device and its stages' keys (Draw.cpp
|
||||
// DrawResourceKey).
|
||||
@@ -2607,6 +2729,8 @@ void ResourceCache::noteMiss(const Key& key) {
|
||||
const auto found = churn.lastByVariant.find(variants);
|
||||
if (found == churn.lastByVariant.end()) {
|
||||
++counts.firstSeen;
|
||||
++counts.window.noCandidate;
|
||||
if (trace) traceMiss(churn, draw, variants, nullptr);
|
||||
churn.lastByVariant.emplace(variants, key);
|
||||
} else {
|
||||
const auto& previous = found->second;
|
||||
@@ -2627,6 +2751,17 @@ void ResourceCache::noteMiss(const Key& key) {
|
||||
std::size_t at = 0;
|
||||
if (!chargeShaderKey(counts, key, at, diff)) ++counts.layout;
|
||||
}
|
||||
ContentKeyDiff parts;
|
||||
++counts.window.compared;
|
||||
if (!ShaderResources::DiffContentKeys(previous, key, draw, parts)) ++counts.window.unlabelled;
|
||||
bool anyPart = false;
|
||||
for (std::size_t i = 0; i < parts.size(); ++i) {
|
||||
if (!parts[i]) continue;
|
||||
++counts.window.differing[i];
|
||||
anyPart = true;
|
||||
}
|
||||
if (!anyPart) ++counts.window.identical;
|
||||
if (trace) traceMiss(churn, draw, variants, &parts);
|
||||
found->second = key;
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
@@ -2651,8 +2786,20 @@ void ResourceCache::noteMiss(const Key& key) {
|
||||
line += item;
|
||||
AgcDriver::ProfilePrint_nid_no_patch("%s\n", line.c_str());
|
||||
};
|
||||
report("dispatch", churn.dispatch);
|
||||
report("draws", churn.draws);
|
||||
if (profile) {
|
||||
report("dispatch", churn.dispatch);
|
||||
report("draws", churn.draws);
|
||||
}
|
||||
const auto keyMiss = [](const char* what, KeyMissWindow& window) {
|
||||
const auto count = [](std::uint64_t value) { return static_cast<unsigned long long>(value); };
|
||||
const auto part = [&](ContentKeyComponent component) { return count(window.differing[static_cast<std::size_t>(component)]); };
|
||||
AgcDriver::ProfilePrint_nid_no_patch("[keymiss] resource-content %s (10 s): misses %llu; differing: layout %llu, V# word0 %llu, V# word1 %llu, V# word2 %llu, V# word3 %llu, image %llu, sampler %llu, shader-data %llu, other %llu, draw target/index %llu; no-candidate %llu; identical %llu; not comparable %llu\n", what, count(window.compared + window.noCandidate), part(ContentKeyComponent::Layout), part(ContentKeyComponent::BufferWord0), part(ContentKeyComponent::BufferWord1), part(ContentKeyComponent::BufferWord2), part(ContentKeyComponent::BufferWord3), part(ContentKeyComponent::Image), part(ContentKeyComponent::Sampler), part(ContentKeyComponent::ShaderData), part(ContentKeyComponent::Other), part(ContentKeyComponent::DrawTarget), count(window.noCandidate), count(window.identical), count(window.unlabelled));
|
||||
window = KeyMissWindow{};
|
||||
};
|
||||
if (trace) {
|
||||
keyMiss("dispatch", churn.dispatch.window);
|
||||
keyMiss("draw", churn.draws.window);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceCache::Insert(const Key& key, std::shared_ptr<ShaderResources> resources, std::vector<std::shared_ptr<ShaderResources>>* evicted) {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
using namespace AgcDriver;
|
||||
using Graphics::ContentKeyComponent;
|
||||
using Graphics::ContentKeyDiff;
|
||||
using Graphics::ShaderResources;
|
||||
using ShaderRecompiler::DescriptorRole;
|
||||
|
||||
namespace {
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
struct Binding {
|
||||
DescriptorRole role;
|
||||
std::vector<std::uint32_t> words;
|
||||
std::uint32_t count = 1;
|
||||
};
|
||||
|
||||
std::vector<std::uint32_t> shaderKey(bool dataWords, bool movable, const std::vector<Binding>& bindings) {
|
||||
std::vector<std::uint32_t> key{dataWords ? 1u : 0u, 0u, 7u, 0u, static_cast<std::uint32_t>(bindings.size())};
|
||||
for (const auto& binding : bindings) {
|
||||
const bool data = binding.role == DescriptorRole::ShaderData || binding.role == DescriptorRole::FlattenedSrt;
|
||||
for (const auto word : {0u, static_cast<std::uint32_t>(binding.role), 0u, 0u, binding.count, 0u, 0u, static_cast<std::uint32_t>(binding.words.size())}) key.push_back(word);
|
||||
const bool emitted = (movable && binding.role == DescriptorRole::GuestBuffers && binding.words.size() == binding.count * 4u) || (dataWords && !movable) || !data;
|
||||
if (emitted) key.insert(key.end(), binding.words.begin(), binding.words.end());
|
||||
for (int flags = 0; flags < 9; ++flags) key.push_back(0u);
|
||||
key.push_back(0u);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> drawKey(std::uint32_t device, const std::vector<std::uint32_t>& stage, std::uint32_t target) {
|
||||
std::vector<std::uint32_t> key{0xffffffffu, device, 0u, 1u, static_cast<std::uint32_t>(stage.size())};
|
||||
key.insert(key.end(), stage.begin(), stage.end());
|
||||
key.push_back(1u);
|
||||
for (const auto word : {target, 0u, 0x100u, 0u, 0u, 0u, 0u, 0u}) key.push_back(word);
|
||||
return key;
|
||||
}
|
||||
|
||||
std::vector<Binding> bindings() {
|
||||
return {{DescriptorRole::GuestBuffers, {10, 11, 12, 13}}, {DescriptorRole::GuestImages, {20, 21, 22, 23, 24, 25, 26, 27}}, {DescriptorRole::GuestSamplers, {30, 31, 32, 33}}, {DescriptorRole::ShaderData, {40, 41, 42, 43}}};
|
||||
}
|
||||
|
||||
void expectOnly(const ContentKeyDiff& diff, std::optional<ContentKeyComponent> component, const char* message) {
|
||||
for (std::size_t i = 0; i < diff.size(); ++i) {
|
||||
check(diff[i] == (component && i == static_cast<std::size_t>(*component)), message);
|
||||
}
|
||||
}
|
||||
|
||||
void testDispatchKey() {
|
||||
ContentKeyDiff diff;
|
||||
const auto base = shaderKey(true, false, bindings());
|
||||
check(ShaderResources::DiffContentKeys(base, base, false, diff), "equal keys were not comparable");
|
||||
expectOnly(diff, std::nullopt, "equal keys reported a difference");
|
||||
const auto change = [&](std::size_t binding, std::size_t word, ContentKeyComponent expected, const char* message) {
|
||||
auto changed = bindings();
|
||||
changed[binding].words[word] += 1;
|
||||
check(ShaderResources::DiffContentKeys(base, shaderKey(true, false, changed), false, diff), message);
|
||||
expectOnly(diff, expected, message);
|
||||
};
|
||||
change(0, 0, ContentKeyComponent::BufferWord0, "V# word 0");
|
||||
change(0, 1, ContentKeyComponent::BufferWord1, "V# word 1");
|
||||
change(0, 2, ContentKeyComponent::BufferWord2, "V# word 2");
|
||||
change(0, 3, ContentKeyComponent::BufferWord3, "V# word 3");
|
||||
change(1, 5, ContentKeyComponent::Image, "image descriptor word");
|
||||
change(2, 1, ContentKeyComponent::Sampler, "sampler word");
|
||||
change(3, 2, ContentKeyComponent::ShaderData, "shader data word");
|
||||
auto two = bindings();
|
||||
two[0].words[0] += 1;
|
||||
two[0].words[3] += 1;
|
||||
check(ShaderResources::DiffContentKeys(base, shaderKey(true, false, two), false, diff), "two components");
|
||||
check(diff[static_cast<std::size_t>(ContentKeyComponent::BufferWord0)] && diff[static_cast<std::size_t>(ContentKeyComponent::BufferWord3)] && !diff[static_cast<std::size_t>(ContentKeyComponent::BufferWord1)], "two components in one miss are both counted");
|
||||
auto fewer = bindings();
|
||||
fewer.pop_back();
|
||||
check(!ShaderResources::DiffContentKeys(base, shaderKey(true, false, fewer), false, diff), "different shapes were compared");
|
||||
expectOnly(diff, ContentKeyComponent::Layout, "shape change is a layout change");
|
||||
const auto noData = shaderKey(false, false, bindings());
|
||||
auto changedData = bindings();
|
||||
changedData[3].words[0] += 1;
|
||||
check(ShaderResources::DiffContentKeys(noData, shaderKey(false, false, changedData), false, diff), "data words left out");
|
||||
expectOnly(diff, std::nullopt, "data words are not part of the key without dataWords");
|
||||
}
|
||||
|
||||
void testDrawKey() {
|
||||
ContentKeyDiff diff;
|
||||
const auto base = drawKey(5, shaderKey(true, true, bindings()), 0x1000);
|
||||
const auto compare = [&](const std::vector<std::uint32_t>& key, std::optional<ContentKeyComponent> expected, const char* message) {
|
||||
check(ShaderResources::DiffContentKeys(base, key, true, diff), message);
|
||||
expectOnly(diff, expected, message);
|
||||
};
|
||||
compare(drawKey(5, shaderKey(true, true, bindings()), 0x2000), ContentKeyComponent::DrawTarget, "draw target");
|
||||
compare(drawKey(6, shaderKey(true, true, bindings()), 0x1000), ContentKeyComponent::Other, "device");
|
||||
auto buffer = bindings();
|
||||
buffer[0].words[0] += 1;
|
||||
compare(drawKey(5, shaderKey(true, true, buffer), 0x1000), ContentKeyComponent::BufferWord0, "draw V# word 0");
|
||||
auto data = bindings();
|
||||
data[3].words[0] += 1;
|
||||
compare(drawKey(5, shaderKey(true, true, data), 0x1000), std::nullopt, "movable draw keys leave data words out");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
testDispatchKey();
|
||||
testDrawKey();
|
||||
std::puts("Key miss attribution passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user