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
2 changes: 1 addition & 1 deletion core/libs/prx/libSceAgcDriver/Execution/include/Recipe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct Recipe {
std::uint64_t dataWordsHash = 0;
// The template's PresyncSurfaces() as of the attach and the host-import identity they were
// computed under (a surface is CPU-read only while its memory has no import).
std::vector<std::pair<std::uint64_t, std::uint64_t>> presyncSurfaces;
std::vector<Graphics::PresyncSurface> presyncSurfaces;
Graphics::HostImportsProof presyncProof;
// Both false for every recipe (only reusable objects get one); kept for the asserts.
bool needsCompletion = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ class VulkanDevice {
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs;
};
static PresentStatistics PresentCounts();
struct PresyncStatistics {
std::uint64_t checked, presyncs, keyChecks, keyWaits;
};
static PresyncStatistics PresyncCounts();
// Stage A of a dispatch's resource build, run WITHOUT GuestMemory::GpuMutex before Dispatch or
// DispatchIndirect: the binding plan, data buffers, imports already serving the guest buffers,
// read-only copies and the descriptor set (see ShaderResources). The dispatch completes it
Expand Down Expand Up @@ -335,7 +339,7 @@ class VulkanDevice {
// Body of Dispatch and DispatchIndirect: `arguments` 0 dispatches x, y, z groups.
IndirectOutcome dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::uint64_t arguments, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t programAddress, std::shared_ptr<PreparedDispatch> prepared, std::shared_ptr<const Recipe>* recipe);
// The stage-A pre-sync over `surfaces` (see PrepareDispatch): the serial waited for, 0 none.
std::uint64_t presync(std::span<const std::pair<std::uint64_t, std::uint64_t>> surfaces);
std::uint64_t presync(std::span<const Graphics::PresyncSurface> surfaces);
std::shared_ptr<ComputePipelineObjects> computePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, VkDescriptorSetLayout setLayout, std::span<const std::uint32_t> layoutKey, VkShaderStageFlags pushStages, std::uint64_t programAddress);
// A DISPATCH_INDIRECT's argument path (see DispatchIndirect): GPU-side from the host import, or
// the counts read on the CPU (the record then continues as a direct dispatch).
Expand Down
29 changes: 24 additions & 5 deletions core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3751,6 +3751,8 @@ struct PresyncCounters {
std::atomic<std::uint64_t> checked{0};
std::atomic<std::uint64_t> presyncs{0};
std::atomic<std::uint64_t> waitedUs{0};
std::atomic<std::uint64_t> keyChecks{0};
std::atomic<std::uint64_t> keyWaits{0};
std::atomic<std::int64_t> lastReport{0};
};

Expand Down Expand Up @@ -4133,23 +4135,31 @@ RecipeOutcome VulkanDevice::DrawFromRecipe(const Graphics::State& graphics, cons
return RecipeOutcome::Recorded;
}

std::uint64_t VulkanDevice::presync(std::span<const std::pair<std::uint64_t, std::uint64_t>> surfaces) {
std::uint64_t VulkanDevice::presync(std::span<const Graphics::PresyncSurface> surfaces) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
auto& counters = Presyncs();
bool overlaps = false;
for (const auto& [address, bytes] : surfaces) overlaps = overlaps || Graphics::Recorder::SnapshotWriteOverlaps(address, static_cast<std::size_t>(bytes));
for (const auto& surface : surfaces) {
const auto bytes = static_cast<std::size_t>(surface.bytes);
if (surface.dccKeys) counters.keyChecks.fetch_add(1, std::memory_order_relaxed);
overlaps = overlaps || (surface.dccKeys ? Graphics::DccKeyWritersPending(surface.address, bytes) : Graphics::Recorder::SnapshotWriteOverlaps(surface.address, bytes));
}
std::uint64_t serial = 0;
bool texelsPending = false;
if (overlaps) {
// No reap here (SubmitAndEpoch's would run completions, which a compute worker must not do
// on queue 0's behalf, see SubmitRecorded): the dispatch reaps after the wait.
std::lock_guard lock(GuestMemory::GpuMutex());
if (state->recorder) {
bool open = false;
for (const auto& [address, bytes] : surfaces) {
const auto info = state->recorder->DescribePendingWrite(address, static_cast<std::size_t>(bytes));
for (const auto& surface : surfaces) {
const auto bytes = static_cast<std::size_t>(surface.bytes);
if (surface.dccKeys && !Graphics::DccKeyWriterNeedsSync(surface.address, bytes)) continue;
const auto info = state->recorder->DescribePendingWrite(surface.address, bytes);
if (!info.has_value()) continue;
open = open || info->open;
serial = std::max(serial, info->serial);
texelsPending = texelsPending || !surface.dccKeys;
}
if (open) serial = state->recorder->SubmitAndEpoch();
}
Expand All @@ -4158,16 +4168,25 @@ std::uint64_t VulkanDevice::presync(std::span<const std::pair<std::uint64_t, std
const auto waitStart = std::chrono::steady_clock::now();
WaitRecorded(serial);
counters.presyncs.fetch_add(1, std::memory_order_relaxed);
if (!texelsPending) counters.keyWaits.fetch_add(1, std::memory_order_relaxed);
counters.waitedUs.fetch_add(static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - waitStart).count()), std::memory_order_relaxed);
}
if (profile) {
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
auto last = counters.lastReport.load();
if (nowMs - last >= 10000 && counters.lastReport.compare_exchange_strong(last, nowMs)) AgcDriver::ProfilePrint_nid_no_patch("[presync] %llu dispatches checked, %llu pre-syncs waited %.0f ms (cumulative)\n", static_cast<unsigned long long>(counters.checked.load()), static_cast<unsigned long long>(counters.presyncs.load()), counters.waitedUs.load() / 1000.0);
if (nowMs - last >= 10000 && counters.lastReport.compare_exchange_strong(last, nowMs)) {
const auto keyWaitCounts = Graphics::KeyWaitCounts();
AgcDriver::ProfilePrint_nid_no_patch("[presync] %llu dispatches checked, %llu pre-syncs waited %.0f ms (cumulative); key ranges %llu checked, %llu pre-syncs that only the keys required; WaitForKeyWriters %llu calls: %llu without a pending writer, %llu known keys, %llu fills leaving uncompressed, %llu synced\n", static_cast<unsigned long long>(counters.checked.load()), static_cast<unsigned long long>(counters.presyncs.load()), counters.waitedUs.load() / 1000.0, static_cast<unsigned long long>(counters.keyChecks.load()), static_cast<unsigned long long>(counters.keyWaits.load()), static_cast<unsigned long long>(keyWaitCounts.calls), static_cast<unsigned long long>(keyWaitCounts.noPending), static_cast<unsigned long long>(keyWaitCounts.known), static_cast<unsigned long long>(keyWaitCounts.fillsUncompressed), static_cast<unsigned long long>(keyWaitCounts.synced));
}
}
return serial;
}

VulkanDevice::PresyncStatistics VulkanDevice::PresyncCounts() {
const auto& counters = Presyncs();
return {counters.checked.load(std::memory_order_relaxed), counters.presyncs.load(std::memory_order_relaxed), counters.keyChecks.load(std::memory_order_relaxed), counters.keyWaits.load(std::memory_order_relaxed)};
}

std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t dispatchThreads, std::uint64_t programAddress) {
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
// APS5_LOCKED_BUILD=1: the whole build under the mutex, as before the split.
Expand Down
10 changes: 10 additions & 0 deletions core/libs/prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ DccKeys ProvedCurrentDccKeys(std::uint64_t metaAddress, std::uint64_t surfaceByt
void ReadTextureSurface(const GuestTextureResource& resource, DccKeys keys, std::span<std::byte> bytes);
void NoteKeysFillOnGpu(std::uint64_t begin, std::size_t count, DccKeys keys);
std::optional<DccKeys> WaitForKeyWriters(const GuestTextureResource& resource, std::uint64_t guestBytes);
bool DccKeyWritersPending(std::uint64_t metaAddress, std::size_t count);
bool DccKeyWriterNeedsSync(std::uint64_t metaAddress, std::size_t count);
struct DccKeyWaitCounts {
std::uint64_t calls;
std::uint64_t noPending;
std::uint64_t known;
std::uint64_t fillsUncompressed;
std::uint64_t synced;
};
DccKeyWaitCounts KeyWaitCounts();

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ struct ImageTableReuseCounts {
};
ImageTableReuseCounts ImageTableReuses();

struct PresyncSurface {
std::uint64_t address;
std::uint64_t bytes;
bool dccKeys;
};

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);
Expand Down Expand Up @@ -280,7 +286,7 @@ class ShaderResources {
// Usable right after the deferred constructor, for address-based builds too (it decodes the
// compiled shader's bindings itself when stage A has not run), and on a completed object from
// the resource cache (its Revalidate repeats the same lookups), without the device lock.
std::vector<std::pair<std::uint64_t, std::uint64_t>> PresyncSurfaces() const;
std::vector<PresyncSurface> PresyncSurfaces() const;

private:
friend class AgcDriver::VulkanDevice;
Expand Down
53 changes: 51 additions & 2 deletions core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,13 +761,62 @@ bool PendingFillsLeaveUncompressed(const Recorder& recorder, const GuestTextureR

}

namespace {

struct KeyWaitTotals {
std::atomic<std::uint64_t> calls{0};
std::atomic<std::uint64_t> noPending{0};
std::atomic<std::uint64_t> known{0};
std::atomic<std::uint64_t> fillsUncompressed{0};
std::atomic<std::uint64_t> synced{0};
};

KeyWaitTotals& KeyWaits() {
static KeyWaitTotals totals;
return totals;
}

}

DccKeyWaitCounts KeyWaitCounts() {
const auto& totals = KeyWaits();
return {totals.calls.load(std::memory_order_relaxed), totals.noPending.load(std::memory_order_relaxed), totals.known.load(std::memory_order_relaxed), totals.fillsUncompressed.load(std::memory_order_relaxed), totals.synced.load(std::memory_order_relaxed)};
}

bool DccKeyWritersPending(std::uint64_t metaAddress, std::size_t count) {
if (metaAddress == 0 || count == 0 || !Recorder::SnapshotWriteOverlaps(metaAddress, count)) return false;
auto& memo = Memo();
std::lock_guard lock(memo.mutex);
MemoRecorder(memo);
return std::none_of(memo.entries.begin(), memo.entries.end(), [&](const GpuKeyStore& store) { return store.note != 0 && store.begin <= metaAddress && metaAddress + count <= store.end; });
}

bool DccKeyWriterNeedsSync(std::uint64_t metaAddress, std::size_t count) {
if (metaAddress == 0 || count == 0 || !GuestMemory::GpuMutex().HeldByThisThread()) return false;
auto* recorder = Recorder::Active();
return recorder != nullptr && recorder->PendingWriteOverlaps(metaAddress, count) && !PendingStoreKeys(*recorder, metaAddress, count).has_value();
}

std::optional<DccKeys> WaitForKeyWriters(const GuestTextureResource& resource, std::uint64_t guestBytes) {
const auto count = static_cast<std::size_t>(guestBytes / KeyBytes);
const auto metaAddress = resource.dccAddress;
if (metaAddress == 0 || count == 0 || !GuestMemory::GpuMutex().HeldByThisThread()) return std::nullopt;
auto& waits = KeyWaits();
waits.calls.fetch_add(1, std::memory_order_relaxed);
auto* recorder = Recorder::Active();
if (recorder == nullptr || !recorder->PendingWriteOverlaps(metaAddress, count) || PendingStoreKeys(*recorder, metaAddress, count).has_value()) return std::nullopt;
if (PendingFillsLeaveUncompressed(*recorder, resource, count)) return DccKeys::Uncompressed;
if (recorder == nullptr || !recorder->PendingWriteOverlaps(metaAddress, count)) {
waits.noPending.fetch_add(1, std::memory_order_relaxed);
return std::nullopt;
}
if (PendingStoreKeys(*recorder, metaAddress, count).has_value()) {
waits.known.fetch_add(1, std::memory_order_relaxed);
return std::nullopt;
}
if (PendingFillsLeaveUncompressed(*recorder, resource, count)) {
waits.fillsUncompressed.fetch_add(1, std::memory_order_relaxed);
return DccKeys::Uncompressed;
}
waits.synced.fetch_add(1, std::memory_order_relaxed);
Recorder::CountSync(2);
recorder->SyncThrough(metaAddress, count);
return std::nullopt;
Expand Down
25 changes: 19 additions & 6 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4337,15 +4337,20 @@ void ShaderResources::finishImageTables() {
bda->SetImageTables(std::move(faults), std::move(ranges));
}

std::vector<std::pair<std::uint64_t, std::uint64_t>> ShaderResources::PresyncSurfaces() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> surfaces;
std::vector<PresyncSurface> ShaderResources::PresyncSurfaces() const {
std::vector<PresyncSurface> surfaces;
// A surface's lookup reads guest memory on the CPU unless it is served GPU-direct from a host
// import: a storage image of imported memory uploads and refreshes from the import, and a
// sampled texture of one views that image (SampledFromStorageEligible), so neither waits for
// the producer.
const auto consider = [&](std::uint32_t format, std::uint64_t address, std::uint64_t guestBytes, bool sampled) {
const bool gpuDirect = sampled ? SampledFromStorageEligible(context, format, address, guestBytes) : HostImportCovers(context, address, static_cast<std::size_t>(guestBytes));
if (!gpuDirect) surfaces.emplace_back(address, guestBytes);
if (!gpuDirect) surfaces.push_back({address, guestBytes, false});
};
static const bool keyRanges = std::getenv("APS5_NO_DCC_PRESYNC") == nullptr;
const auto considerKeys = [&](std::uint64_t dccAddress, std::uint64_t guestBytes) {
const auto keyBytes = DccKeyBytes(guestBytes);
if (keyRanges && dccAddress != 0 && keyBytes != 0) surfaces.push_back({dccAddress, keyBytes, true});
};
if (completed) {
// A cached object (the resource cache): Revalidate repeats the lookups of the objects the
Expand All @@ -4361,18 +4366,22 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> ShaderResources::PresyncSur
const auto& texture = textures[textureIndex++];
if (texture->ViewsStorageImage()) consider(range.format, range.address, range.bytes, false);
else consider(range.format, range.address, range.bytes, true);
considerKeys(range.dccAddress, range.bytes);
}
}
for (std::size_t index = 0; index < storageTextures.size(); ++index) {
if (index != 0 && storageTextures[index] == storageTextures[index - 1]) continue;
const auto& image = *storageTextures[index];
consider(image.Descriptor().format, image.Descriptor().baseAddress, image.GuestBytes(), false);
considerKeys(image.Descriptor().dccAddress, image.GuestBytes());
}
return surfaces;
}
if (!imageRecords.empty()) {
for (const auto& record : imageRecords) {
if (record.decoded) consider(record.resource.format, record.resource.baseAddress, record.guestBytes, record.sampled);
if (!record.decoded) continue;
consider(record.resource.format, record.resource.baseAddress, record.guestBytes, record.sampled);
considerKeys(record.resource.dccAddress, record.guestBytes);
}
return surfaces;
}
Expand All @@ -4381,14 +4390,18 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> ShaderResources::PresyncSur
forEachImageElement(*deferredCompute.program, [&](const ShaderRecompiler::DescriptorBinding& binding, std::uint32_t, std::span<const std::uint32_t> words) {
if (IsNullTextureResource(words)) return;
const auto resource = DecodeTextureResource(words);
consider(resource.format, resource.baseAddress, DescribeSurface(resource).guestBytes, binding.kind == ShaderRecompiler::DescriptorKind::SampledImage);
const auto guestBytes = DescribeSurface(resource).guestBytes;
consider(resource.format, resource.baseAddress, guestBytes, binding.kind == ShaderRecompiler::DescriptorKind::SampledImage);
considerKeys(resource.dccAddress, guestBytes);
});
for (const auto& binding : deferredCompute.program->bindings) {
if (binding.role != ShaderRecompiler::DescriptorRole::ImageTable) continue;
for (std::size_t element = 0; element < binding.guestDescriptor.size() / 8u; ++element) {
try {
const auto resource = DecodeTextureResource(std::span<const std::uint32_t>(binding.guestDescriptor).subspan(element * 8u, 8u));
consider(resource.format, resource.baseAddress, DescribeSurface(resource).guestBytes, true);
const auto guestBytes = DescribeSurface(resource).guestBytes;
consider(resource.format, resource.baseAddress, guestBytes, true);
considerKeys(resource.dccAddress, guestBytes);
} catch (const std::runtime_error&) {
}
}
Expand Down
Loading
Loading