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: 3 additions & 2 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4166,7 +4166,7 @@ struct DispatchCounters {
std::array<double, DispatchRows> phaseTotals{};
std::uint64_t profiledDispatches = 0;
IndirectHold indirectHold;
std::uint64_t cacheHits = 0, cacheMisses = 0, cacheInvalidated = 0, templateRefreshed = 0, templateSameWords = 0;
std::uint64_t cacheHits = 0, cacheMisses = 0, cacheInvalidated = 0, templateRefreshed = 0, templateSameWords = 0, templateByNumber = 0;
double templateRevalidateMs = 0;
std::uint64_t procLookups = 0, preBarriersRecorded = 0, preBarriersSkipped = 0;
std::chrono::steady_clock::time_point cacheReport = std::chrono::steady_clock::now();
Expand Down Expand Up @@ -4809,6 +4809,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
// after an earlier dispatch's reads of the buffers by that dispatch's trailing barrier. A
// recipe compares the two 64-bit hashes first: equal hashes mean the buffers hold the words.
const bool differs = record.dataRefresh == RecordedDispatch::DataRefresh::Words || resources.DataWordsHash() != record.dataWordsHash;
if (timer.profile && record.shader->program != nullptr && record.shader->program->bindings.size() != resources.BindingCount()) ++d.templateByNumber;
if (differs && (record.resourceKeyMatched ? resources.refreshData(commands, *record.shader, &recorder) : resources.RefreshData(commands, *record.shader, &recorder))) {
covered = 0;
++d.templateRefreshed;
Expand Down Expand Up @@ -5319,7 +5320,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
}
if (profile && std::chrono::steady_clock::now() - d.cacheReport > std::chrono::seconds(10)) {
d.cacheReport = std::chrono::steady_clock::now();
AgcDriver::ProfilePrint_nid_no_patch("[rescache] (cumulative): %llu hits, %llu misses, %llu invalidated, %zu entries (dispatch + draw); template hits: %llu refreshed the data buffers (revalidate %.1f ms), %llu had the same words; Find calls %llu, Touch calls %llu\n", static_cast<unsigned long long>(d.cacheHits), static_cast<unsigned long long>(d.cacheMisses), static_cast<unsigned long long>(d.cacheInvalidated), state->resourceCache.Size(), static_cast<unsigned long long>(d.templateRefreshed), d.templateRevalidateMs, static_cast<unsigned long long>(d.templateSameWords), static_cast<unsigned long long>(ResourceCache::Finds()), static_cast<unsigned long long>(ResourceCache::Touches()));
AgcDriver::ProfilePrint_nid_no_patch("[rescache] (cumulative): %llu hits, %llu misses, %llu invalidated, %zu entries (dispatch + draw); template hits: %llu refreshed the data buffers (revalidate %.1f ms), %llu had the same words, %llu paired their data bindings by number (no image table binding of their own); Find calls %llu, Touch calls %llu\n", static_cast<unsigned long long>(d.cacheHits), static_cast<unsigned long long>(d.cacheMisses), static_cast<unsigned long long>(d.cacheInvalidated), state->resourceCache.Size(), static_cast<unsigned long long>(d.templateRefreshed), d.templateRevalidateMs, static_cast<unsigned long long>(d.templateSameWords), static_cast<unsigned long long>(d.templateByNumber), static_cast<unsigned long long>(ResourceCache::Finds()), static_cast<unsigned long long>(ResourceCache::Touches()));
AgcDriver::ProfilePrint_nid_no_patch("[vk] deviceProc lookups inside the device call (cumulative): %llu (%.2f per dispatch); pre-dispatch barriers recorded %llu, skipped %llu; compute pipelines created %llu (%llu under the GPU mutex)\n", static_cast<unsigned long long>(d.procLookups), d.profiledDispatches != 0 ? static_cast<double>(d.procLookups) / static_cast<double>(d.profiledDispatches) : 0.0, static_cast<unsigned long long>(d.preBarriersRecorded), static_cast<unsigned long long>(d.preBarriersSkipped), static_cast<unsigned long long>(computePipelinesCreated.load(std::memory_order_relaxed)), static_cast<unsigned long long>(computePipelinesCreatedLocked.load(std::memory_order_relaxed)));
reportRecipes();
reportDispatchMove();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ class ShaderResources {
}
// Whether RefreshData would record anything for `shader` (the per-word compare; verification).
bool DataWordsDiffer(const CompiledShader& shader) const;
std::size_t BindingCount() const { return bindings.size(); }
// Why the fast proof of a Revalidate left the object to the full walk (the [rescache]
// revalidate line's reasons); Count: it did not.
enum class FastFail : std::size_t { NoRecord, Collect, Pending, Evicted, Changed, Keys, ClearedView, StorageKeys, Depth, Replaced, Audit, Count };
Expand Down
21 changes: 10 additions & 11 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4304,12 +4304,11 @@ std::uint64_t ShaderResources::DataWordsHash(const CompiledShader& shader) {
bool ShaderResources::DataWordsDiffer(const CompiledShader& shader) const {
Require(shader.program != nullptr, "missing compiled shader");
const auto& program = *shader.program;
if (program.bindings.size() != bindings.size()) return true;
for (std::size_t index = 0; index < program.bindings.size(); ++index) {
const auto& binding = program.bindings[index];
for (const auto& binding : program.bindings) {
if (!DataRole(binding.role)) continue;
if (bindings[index].allocations.size() != 1) return true;
const auto& allocation = allocations[bindings[index].allocations.front()];
const auto held = std::ranges::find_if(bindings, [&](const Binding& candidate) { return candidate.layout.binding == binding.binding; });
if (held == bindings.end() || held->allocations.size() != 1) return true;
const auto& allocation = allocations[held->allocations.front()];
if (allocation.dataWords.size() != binding.guestDescriptor.size() || !std::equal(allocation.dataWords.begin(), allocation.dataWords.end(), binding.guestDescriptor.begin())) return true;
}
return false;
Expand All @@ -4324,23 +4323,23 @@ bool ShaderResources::RefreshData(VkCommandBuffer commands, const CompiledShader
bool ShaderResources::refreshData(VkCommandBuffer commands, const CompiledShader& shader, Recorder* recorder) {
Require(shader.program != nullptr, "missing compiled shader");
const auto& program = *shader.program;
Require(program.bindings.size() == bindings.size(), "template bindings disagree with the shader");
bool recorded = false;
// One [gputime] range of class TemplateDataRefresh around the updates, begun at the first one
// (a refresh that finds every word equal records nothing and times nothing).
auto timing = Recorder::NoTiming;
std::uint64_t refreshedBytes = 0;
for (std::size_t index = 0; index < program.bindings.size(); ++index) {
const auto& binding = program.bindings[index];
for (const auto& binding : program.bindings) {
if (!DataRole(binding.role)) continue;
Require(bindings[index].allocations.size() == 1, "data binding without its buffer");
auto& allocation = allocations[bindings[index].allocations.front()];
const auto held = std::ranges::find_if(bindings, [&](const Binding& candidate) { return candidate.layout.binding == binding.binding; });
Require(held != bindings.end(), "template bindings disagree with the shader");
Require(held->allocations.size() == 1, "data binding without its buffer");
auto& allocation = allocations[held->allocations.front()];
const auto size = binding.guestDescriptor.size() * sizeof(std::uint32_t);
Require(allocation.buffer != nullptr && !allocation.guest && allocation.size == size && size <= MaxRefreshBytes, "template data buffer cannot take the dispatch's words");
if (allocation.dataWords.size() == binding.guestDescriptor.size() && std::equal(allocation.dataWords.begin(), allocation.dataWords.end(), binding.guestDescriptor.begin())) continue;
if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData) ValidateRuntimeResources(shader, binding.guestDescriptor);
if (recorder != nullptr && timing == Recorder::NoTiming) timing = recorder->BeginGpuTiming(Recorder::CommandClass::TemplateDataRefresh);
writeDataWords(commands, bindings[index].allocations.front(), binding.guestDescriptor);
writeDataWords(commands, held->allocations.front(), binding.guestDescriptor);
allocation.dataWords.assign(binding.guestDescriptor.begin(), binding.guestDescriptor.end());
refreshedBytes += size;
recorded = true;
Expand Down
36 changes: 36 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6806,6 +6806,41 @@ void misalignedShaderDataTests() {
Require(mock.live == 0, "misaligned shader data resources leaked Vulkan objects");
}

void imageTableTemplateRefreshTests() {
namespace Abi = ShaderRecompiler::ImageTableAbi;
mock = MockVulkan{};
auto context = mockContext();
context.imageTableCapacity = 2;
context.limits.maxDescriptorSetSampledImages = 2;
context.limits.maxPerStageDescriptorSampledImages = 2;
const auto commands = reinterpret_cast<VkCommandBuffer>(std::uintptr_t{1});
{
ShaderRecompiler::RecompileResult compute;
auto map = makeBinding(Role::ImageTableMap, static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::ImageTableMap), 1, std::vector<std::uint32_t>(Abi::HeaderWords));
map.guestDescriptor[Abi::HeaderVersion] = Abi::Version;
map.guestDescriptor[Abi::HeaderElementFaults] = Abi::HeaderWords;
map.guestDescriptor[Abi::HeaderSamplerFaults] = Abi::HeaderWords;
compute.bindings.push_back(map);
auto images = makeBinding(Role::ImageTable, ShaderRecompiler::RuntimeAbi::ImageTableBinding, 0, {});
images.kind = Kind::SampledImage;
compute.bindings.push_back(images);
compute.bindings.push_back(makeBinding(Role::ShaderData, 1, 1, ShaderDataWords({0x11, 0x22})));
compute.shaderDataDwords = ShaderRecompiler::RuntimeAbi::ShaderDataDwords;
auto live = compute;
live.bindings[2].guestDescriptor = ShaderDataWords({0x33, 0x44});
const AgcDriver::Graphics::CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &compute, 0};
const AgcDriver::Graphics::CompiledShader liveShader{ShaderRecompiler::ShaderStage::Compute, &live, 0};
AgcDriver::Graphics::ShaderResources resources(context, shader);
const auto data = findWrite(1).buffers.at(0).buffer;
Require(!resources.DataWordsDiffer(shader), "a template's own words differ from the shader that built it");
Require(resources.DataWordsDiffer(liveShader), "a template with an image table missed the dispatch's new words");
Require(resources.RefreshData(commands, liveShader), "a refresh of a template with an image table recorded nothing");
Require(sameBytes(bufferBytes(data), live.bindings[2].guestDescriptor.data(), 3u * sizeof(std::uint32_t)), "a refresh of a template with an image table did not write the dispatch's words");
Require(!resources.DataWordsDiffer(liveShader), "the refreshed template's words are not the dispatch's");
}
Require(mock.live == 0, "image table template refresh resources leaked Vulkan objects");
}

struct ModuleShape {
bool fragment = false;
std::optional<std::uint32_t> fragmentMode;
Expand Down Expand Up @@ -11245,6 +11280,7 @@ int main(int argc, char** argv) {
samplerTableDeviceLimitTests();
tableUpdateAfterBindTests();
misalignedShaderDataTests();
imageTableTemplateRefreshTests();
debugBranchTests();
hostImportBudgetTests();
textureCacheBudgetTests();
Expand Down
Loading