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
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ class Driver {
static void countIndirect(int path, double readMs);
static int indirectArgumentsPath(std::span<const std::uint32_t> packet, std::uint64_t arguments);
void dispatchIndirect(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission);
enum class AheadArguments { None, Matched, Differed };
struct IndirectSource {
std::uint64_t arguments = 0;
int path = IndirectGpu;
std::uint32_t queue = 0;
AheadArguments ahead = AheadArguments::None;
};
static IndirectSource& indirectSource();
static std::string describeIndirectSource();
std::array<std::uint32_t, 5> readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator);
static bool& sampledRead();
static bool writeEvidenceEnabled();
static bool writerKeyedEvidence();
Expand Down Expand Up @@ -277,7 +287,7 @@ class Driver {
std::chrono::steady_clock::time_point resolvedAt{};
};
struct GroupCaptureStats {
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0;
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0;
std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,10 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
resolved = ahead.packet;
packet = resolved;
indirectArguments = ahead.indirectArguments;
} else if (indirectArguments != 0 && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) {

recordQueuedLabelsBeforeRead(submission.queue);
} else if (indirectArguments != 0 && !resolving && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) {
indirectSource().path = IndirectFillKernel;
const auto readStart = std::chrono::steady_clock::now();
resolved = Pm4::ReadDispatchArguments(indirectArguments, packet[4]);
resolved = readDispatchArguments(submission.queue, indirectArguments, packet[4]);
countIndirect(IndirectFillKernel, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count());
packet = resolved;
indirectArguments = 0;
Expand Down Expand Up @@ -435,7 +434,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
char where[64];
std::snprintf(where, sizeof(where), "compute shader 0x%llx: ", static_cast<unsigned long long>(address));
throw std::runtime_error(where + std::string(error.what()));
throw std::runtime_error(where + std::string(error.what()) + describeIndirectSource());
};
phaseTiming.Phase(PhaseSnapshots);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ void Driver::reportGroupCapture(std::uint32_t queue) {
auto& stats = groupCaptureStats();
const auto now = std::chrono::steady_clock::now();
if (now - stats.reported < std::chrono::seconds(10)) return;
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device\n",
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
queue, static_cast<unsigned long long>(stats.runs), static_cast<unsigned long long>(stats.resolved), static_cast<unsigned long long>(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs,
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches),
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.deviceRejects));
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.deviceRejects), static_cast<unsigned long long>(stats.argumentMismatches));
stats = GroupCaptureStats{};
stats.reported = now;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/IndirectDispatch.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
#include <cstdio>
#include <cstdlib>

namespace AgcDriver::DriverDetail {

namespace {

const char* IndirectPathName(int path) {
switch (path) {
case IndirectGpu: return "gpu-side";
case IndirectPendingImage: return "pending image results";
case IndirectCopiedWrite: return "pending label or copied write";
case IndirectNotImported: return "not imported";
case IndirectThreadDimensions: return "thread dimensions";
case IndirectFillKernel: return "fill kernel";
case IndirectMisaligned: return "misaligned";
case IndirectDisabled: return "disabled";
case IndirectWorkgroupMemory: return "device memory LDS";
default: return "unknown";
}
}

}

void Driver::countIndirect(int path, double readMs) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
if (!profile || path < 0 || path >= IndirectPaths) return;
Expand All @@ -31,23 +53,56 @@ int Driver::indirectArgumentsPath(std::span<const std::uint32_t> packet, std::ui
return IndirectGpu;
}

Driver::IndirectSource& Driver::indirectSource() {
static thread_local IndirectSource source;
return source;
}

std::string Driver::describeIndirectSource() {
const auto& source = indirectSource();
if (source.arguments == 0) return {};
const char* ahead = source.ahead == AheadArguments::Matched ? "adopted, its counts matched the re-read" : source.ahead == AheadArguments::Differed ? "dropped, its counts differed from the re-read" : "none";
char text[256];
std::snprintf(text, sizeof(text), "; DISPATCH_INDIRECT on queue 0x%x, arguments at 0x%llx, driver path %s, read-ahead %s", source.queue, static_cast<unsigned long long>(source.arguments), IndirectPathName(source.path), ahead);
return text;
}

std::array<std::uint32_t, 5> Driver::readDispatchArguments(std::uint32_t queue, std::uint64_t arguments, std::uint32_t initiator) {
recordQueuedLabelsBeforeRead(queue);
if (!resolvingAhead()) {
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Indirect);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
Graphics::FinishLeaseWork();
}
return Pm4::ReadDispatchArguments(arguments, initiator);
}

void Driver::dispatchIndirect(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission) {
const auto arguments = Pm4::DispatchArgumentAddress(packet, queue);
const auto initiator = packet.back();
const int path = indirectArgumentsPath(packet, arguments);
struct Scope {
IndirectSource& source;
IndirectSource previous;
~Scope() { source = previous; }
} scope{indirectSource(), indirectSource()};
scope.source = IndirectSource{arguments, path, submission.queue, AheadArguments::None};
if (path != IndirectGpu) {
const auto readStart = std::chrono::steady_clock::now();
const auto direct = readDispatchArguments(submission.queue, arguments, initiator);
countIndirect(path, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count());
if (!resolvingAhead()) {
const auto& resolved = resolvedAhead();
auto& resolved = resolvedAhead();
if (const auto found = resolved.find(currentPacketOffset()); found != resolved.end() && found->second.indirectArguments == 0 && found->second.packet[0] == 0xc0031500u) {
const auto direct = found->second.packet;
dispatch(queue, direct, submission);
return;
if (found->second.packet == direct) {
scope.source.ahead = AheadArguments::Matched;
} else {
resolved.erase(found);
++groupCaptureStats().argumentMismatches;
scope.source.ahead = AheadArguments::Differed;
}
}
}
recordQueuedLabelsBeforeRead(submission.queue);
const auto readStart = std::chrono::steady_clock::now();
const auto direct = Pm4::ReadDispatchArguments(arguments, initiator);
countIndirect(path, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count());
dispatch(queue, direct, submission);
return;
}
Expand Down
17 changes: 11 additions & 6 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4258,6 +4258,8 @@ struct RecordedDispatch {
bool refreshed = false;
bool resourceKeyMatched = false;
std::shared_ptr<Graphics::ShaderResources::DrawBindings> bindings{};
std::uint64_t cpuArguments = 0;
int cpuReason = 0;
};

VkDevice VulkanDevice::Device() const {
Expand Down Expand Up @@ -4562,15 +4564,16 @@ void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& out
}
timer.phase(PhaseDecide);
if (outcome.cpuReason == 0) return;
// The read waits for the producing batch through the flush hook, as the driver's resolve
// did for every indirect dispatch before; the counts then go through the direct checks.
const auto readStart = std::chrono::steady_clock::now();
Graphics::FinishLeaseWork();
std::array<std::uint32_t, 3> groups{};
GuestMemory::Read(arguments, std::as_writable_bytes(std::span(groups)), 4);
outcome.argumentReadMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - readStart).count();
record.x = groups[0];
record.y = groups[1];
record.z = groups[2];
record.cpuArguments = arguments;
record.cpuReason = outcome.cpuReason;
// Timed before `arguments` is cleared, so the read counts as an indirect phase.
timer.phase(PhaseArgumentRead);
// From here on the phases of a CPU-resolved indirect dispatch are charged as direct ones.
Expand Down Expand Up @@ -4703,15 +4706,17 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) {
context.Resolved(&Graphics::DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, record.x, record.y, record.z);
} else {
char counted[128] = "";
if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast<unsigned long long>(record.cpuArguments), record.cpuReason);
if (record.shader->program->workgroupMemoryDwords != 0) {
char text[256];
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, limit[0], limit[1], limit[2]);
char text[384];
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]);
throw std::runtime_error(text);
}
const auto offsets = std::ranges::count_if(record.shader->program->specialization, [](const auto& constant) { return ShaderRecompiler::PipelineSpecialization::IsWorkgroupOffset(constant.id); });
if (offsets != ShaderRecompiler::PipelineSpecialization::WorkgroupOffsetWords) {
char text[192];
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u exceeds device limits %ux%ux%u and the shader takes no workgroup offset", record.x, record.y, record.z, limit[0], limit[1], limit[2]);
char text[320];
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and the shader takes no workgroup offset", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]);
throw std::runtime_error(text);
}
const auto bindPipeline = context.Resolved(&Graphics::DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ std::uint64_t ImageMirrorSerial(const Context& context, std::uint64_t address, s
// prints the [address-sync] leases line every 10 s from LeaseCounters (cumulative).
bool SyncLeaseWork();
void CountLeaseOutcome(bool synced, std::uint64_t batchSerial);
void FinishLeaseWork();
struct LeaseStats {
std::uint64_t deferred = 0;
std::uint64_t synced = 0;
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2808,6 +2808,17 @@ void CountLeaseOutcome(bool synced, std::uint64_t batchSerial) {
AgcDriver::ProfilePrint_nid_no_patch("[address-sync] leases: %llu released at completion, %llu synced at once; %llu pin-contention waits by guest threads (%llu finished the lease batch, %llu drained the recorder, %llu cache-only drops) %.1f s; BDA table cache %llu hits / %llu misses (%zu tables held); snapshot compares: %llu skipped (live-backed region), %llu made\n", static_cast<unsigned long long>(stats.deferred), static_cast<unsigned long long>(stats.synced), static_cast<unsigned long long>(stats.contentionWaits), static_cast<unsigned long long>(stats.contentionSyncs), static_cast<unsigned long long>(stats.contentionDrains), static_cast<unsigned long long>(stats.cacheDrops), stats.contentionMs / 1000, static_cast<unsigned long long>(table.hits), static_cast<unsigned long long>(table.misses), table.held, static_cast<unsigned long long>(snapshots.skipped.load(std::memory_order_relaxed)), static_cast<unsigned long long>(snapshots.checked.load(std::memory_order_relaxed)));
}

void FinishLeaseWork() {
GuestMemory::AssertGpuLockHeld("FinishLeaseWork");
const auto& state = Leases();
auto* recorder = Recorder::Active();
const auto target = state.newestLeaseSerial;
if (recorder == nullptr || recorder != state.leaseRecorder || target <= recorder->CompletedThrough()) return;
if (target >= recorder->OpenSerial()) recorder->Submit();
Recorder::AnnounceSyncSite(__builtin_return_address(0));
recorder->FinishUpTo(target);
}

LeaseStats LeaseCounters() {
auto& state = Leases();
std::lock_guard lock(state.mutex);
Expand Down
Loading
Loading