Merge pull request #192 from MichonGoddijn231849/perf/validation-failure-memo
perf(agc): remember shader validation failures and reject repeats without throwing
This commit is contained in:
commit
a67b644ff6
|
|
@ -273,6 +273,7 @@ public:
|
|||
// `recipe`, when given, receives the DrawRecipe a recorded, cacheable, reusable, direct draw
|
||||
// built for its draw-cache entry (design_cpu_final M8); null otherwise.
|
||||
void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {}, std::shared_ptr<const DrawRecipe>* recipe = nullptr);
|
||||
std::optional<std::string> KnownDrawRejection(const Graphics::State& graphics, std::span<const Graphics::CompiledShader> shaders) const;
|
||||
// A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after
|
||||
// the packet's labels; Rebuild when the recipe's device is not this one or DrawWithRecipe
|
||||
// missed (nothing recorded: the caller runs Draw with the hit's stages and re-attaches).
|
||||
|
|
|
|||
|
|
@ -4866,6 +4866,10 @@ private:
|
|||
fold(*programResults[0], direct);
|
||||
if (graphics.rectList && patched.contains(0)) buildRectList();
|
||||
snapshot();
|
||||
if (auto known = localDevice->KnownDrawRejection(graphics, stages)) {
|
||||
rejected = std::move(*known);
|
||||
return DrawVerdict::Rejected;
|
||||
}
|
||||
lockForDraw();
|
||||
noteDrawWriters(stages, submission.queue);
|
||||
phase(DrawRowVectors);
|
||||
|
|
@ -4891,6 +4895,12 @@ private:
|
|||
recipe = findDrawRecipe(drawKey, recipeStages);
|
||||
if (recipe == nullptr) VulkanDevice::NoteDrawRecipeMiss(VulkanDevice::DrawRecipePrecheck::NoRecipe);
|
||||
}
|
||||
if (recipe == nullptr) {
|
||||
if (auto known = localDevice->KnownDrawRejection(graphics, stages)) {
|
||||
rejected = std::move(*known);
|
||||
return DrawVerdict::Rejected;
|
||||
}
|
||||
}
|
||||
lockForDraw();
|
||||
noteDrawWriters(stages, submission.queue);
|
||||
phase(DrawRowVectors);
|
||||
|
|
|
|||
|
|
@ -2196,6 +2196,10 @@ VulkanDevice::IndirectDrawSupport VulkanDevice::DrawIndirectSupport() const {
|
|||
return {state->drawIndirectFirstInstance, state->multiDrawIndirect, state->drawIndirectCount};
|
||||
}
|
||||
|
||||
std::optional<std::string> VulkanDevice::KnownDrawRejection(const Graphics::State& graphics, std::span<const Graphics::CompiledShader> shaders) const {
|
||||
return Graphics::KnownValidationFailure(graphicsContext(), shaders, graphics);
|
||||
}
|
||||
|
||||
void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::shared_ptr<const DrawRecipe>* recipe) {
|
||||
// Two stdout lines per draw cost ~1.3 ms per frame of the queue-0 worker (part of it under the
|
||||
// GPU mutex); APS5_TRACE_DRAWS=1 restores them.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include <string>
|
||||
#include <optional>
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DRAW_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DRAW_HPP
|
||||
|
||||
|
|
@ -13,6 +15,7 @@ namespace AgcDriver::Graphics {
|
|||
// built for its draw-cache entry (design_cpu_final M8; null otherwise, and always under
|
||||
// APS5_NO_DRAW_RECIPE=1).
|
||||
void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots = {}, std::shared_ptr<const DrawRecipe>* recipe = nullptr);
|
||||
std::optional<std::string> KnownValidationFailure(const Context& context, std::span<const CompiledShader> shaders, const State& state);
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress);
|
||||
|
||||
|
|
|
|||
|
|
@ -369,12 +369,9 @@ void countCache(std::uint64_t DrawProfile::*counter) {
|
|||
// fragment results the key already names (the pipeline key treats them the same way). `memoized`
|
||||
// says whether the memo applied, `hit` whether it answered. APS5_NO_VALIDATE_CACHE=1 validates
|
||||
// every draw.
|
||||
std::set<std::uint32_t> CachedFragmentOutputs(const Context& context, std::span<const CompiledShader> shaders, const State& state, bool& memoized, bool& hit) {
|
||||
bool ValidationKey(const Context& context, std::span<const CompiledShader> shaders, const State& state, std::vector<std::uint64_t>& key) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
static const bool disabled = std::getenv("APS5_NO_VALIDATE_CACHE") != nullptr;
|
||||
memoized = false;
|
||||
hit = false;
|
||||
std::vector<std::uint64_t> key;
|
||||
const auto add = [&](auto value) { key.push_back(static_cast<std::uint64_t>(value)); };
|
||||
bool keyed = !disabled;
|
||||
if (keyed) {
|
||||
|
|
@ -435,19 +432,47 @@ std::set<std::uint32_t> CachedFragmentOutputs(const Context& context, std::span<
|
|||
add(tessellation.outputTopology);
|
||||
}
|
||||
}
|
||||
return keyed;
|
||||
}
|
||||
|
||||
std::mutex& validationMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::map<std::vector<std::uint64_t>, std::string>& validationFailures() {
|
||||
static std::map<std::vector<std::uint64_t>, std::string> failures;
|
||||
return failures;
|
||||
}
|
||||
|
||||
std::set<std::uint32_t> CachedFragmentOutputs(const Context& context, std::span<const CompiledShader> shaders, const State& state, bool& memoized, bool& hit) {
|
||||
memoized = false;
|
||||
hit = false;
|
||||
std::vector<std::uint64_t> key;
|
||||
const bool keyed = ValidationKey(context, shaders, state, key);
|
||||
static std::map<std::vector<std::uint64_t>, std::set<std::uint32_t>> memo;
|
||||
if (keyed) {
|
||||
memoized = true;
|
||||
std::lock_guard lock(mutex);
|
||||
std::lock_guard lock(validationMutex());
|
||||
if (const auto found = memo.find(key); found != memo.end()) {
|
||||
hit = true;
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
auto outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing);
|
||||
std::set<std::uint32_t> outputs;
|
||||
try {
|
||||
outputs = ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric, context.descriptorIndexing);
|
||||
} catch (const std::exception& error) {
|
||||
if (keyed) {
|
||||
std::lock_guard lock(validationMutex());
|
||||
auto& failures = validationFailures();
|
||||
if (failures.size() >= 1024) failures.clear();
|
||||
failures.emplace(std::move(key), error.what());
|
||||
}
|
||||
throw;
|
||||
}
|
||||
if (keyed) {
|
||||
std::lock_guard lock(mutex);
|
||||
std::lock_guard lock(validationMutex());
|
||||
// A handful of configurations recur; a runaway key space is dropped wholesale.
|
||||
if (memo.size() >= 1024) memo.clear();
|
||||
memo.emplace(std::move(key), outputs);
|
||||
|
|
@ -1238,6 +1263,15 @@ bool RecordDraws() {
|
|||
|
||||
}
|
||||
|
||||
std::optional<std::string> KnownValidationFailure(const Context& context, std::span<const CompiledShader> shaders, const State& state) {
|
||||
std::vector<std::uint64_t> key;
|
||||
if (!ValidationKey(context, shaders, state, key)) return std::nullopt;
|
||||
std::lock_guard lock(validationMutex());
|
||||
const auto& failures = validationFailures();
|
||||
if (const auto found = failures.find(key); found != failures.end()) return found->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots, std::shared_ptr<const DrawRecipe>* recipeOut) {
|
||||
PerformanceTimer timing("Graphics.Draw");
|
||||
// APS5_PROFILE_DRAW prints the time of each phase of the draw (microseconds) and the [draws] totals.
|
||||
|
|
|
|||
Loading…
Reference in New Issue