perf(agc): resolve a sampled element repeating the one before it to the same texture
A build's sampled image elements often repeat the element just before them in plan order (the same eight descriptor words). Each still took its own stage-A record (decode, collect, DCC key read, cache entry) and its own stage-B lookup. The lookups run in plan order and nothing runs between two consecutive ones, so a repeated element's lookup would find the state the previous lookup just left: the entry it validated or made, current against the same collect. Such an element now binds the previous element's texture (when the depth-compare flag matches too, which is part of the cache key), and stage A gives it a record of the decoded descriptor only. An element repeating an earlier but not the immediately preceding one still takes its own lookup, as does any element after a storage image lookup.
This commit is contained in:
parent
0209c64245
commit
63fb5779b3
|
|
@ -335,6 +335,12 @@ private:
|
|||
std::uint64_t entryGeneration = 0;
|
||||
};
|
||||
std::vector<ImageRecord> imageRecords;
|
||||
struct PreviousSampled {
|
||||
std::array<std::uint32_t, 8> words{};
|
||||
bool depthCompare = false;
|
||||
std::shared_ptr<Texture> texture;
|
||||
};
|
||||
PreviousSampled previousSampled;
|
||||
// The next record resolveImageBinding consumes (records follow the deferredImages order).
|
||||
std::size_t nextImageRecord = 0;
|
||||
// Stage B: the record's texture when the fastRevalidate predicate proves it current under the
|
||||
|
|
|
|||
|
|
@ -1421,8 +1421,10 @@ void ShaderResources::buildComplete() {
|
|||
try {
|
||||
// The lookups run in plan order: Revalidate walks the bindings the same way, and consecutive
|
||||
// storage elements of one mip chain share the previous element's image.
|
||||
previousSampled = {};
|
||||
for (auto& source : samplerSources) source.singleLevelImage = source.mipmappedImage = false;
|
||||
for (const auto& deferred : deferredImages) resolveImageBinding(*deferred.binding, bindings[deferred.index], std::span<const std::shared_ptr<Sampler>>(samplers).subspan(deferred.firstSampler, deferred.samplerCount));
|
||||
previousSampled = {};
|
||||
deferredImages.clear();
|
||||
applyAnisoOverride();
|
||||
// The records served their purpose: each holds the cache entry's objects as of stage A,
|
||||
|
|
@ -2940,8 +2942,23 @@ bool ShaderResources::precollectImages() {
|
|||
const auto elementWords = binding.guestDescriptor.size() / binding.count;
|
||||
for (std::uint32_t element = 0; element < binding.count; ++element) {
|
||||
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
|
||||
const bool sampled = binding.kind == ShaderRecompiler::DescriptorKind::SampledImage;
|
||||
if (sampled && !imageRecords.empty()) {
|
||||
const auto& previous = imageRecords.back();
|
||||
if (previous.sampled && previous.decoded && words.size() == 8 && std::equal(words.begin(), words.end(), previous.words.begin()) && (binding.imageDepthCompare.empty() || !binding.imageDepthCompare.at(element))) {
|
||||
ImageRecord repeated;
|
||||
repeated.sampled = true;
|
||||
repeated.decoded = previous.decoded;
|
||||
repeated.words = previous.words;
|
||||
repeated.resource = previous.resource;
|
||||
repeated.guestBytes = previous.guestBytes;
|
||||
repeated.components = previous.components;
|
||||
imageRecords.push_back(repeated);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ImageRecord record;
|
||||
record.sampled = binding.kind == ShaderRecompiler::DescriptorKind::SampledImage;
|
||||
record.sampled = sampled;
|
||||
if (words[0] != 0u || (words[1] & 0xffu) != 0u) {
|
||||
const auto* memo = words.size() == 8 ? &decodedImage(context.device, words) : nullptr;
|
||||
record.resource = memo != nullptr ? memo->resource : DecodeTextureResource(words);
|
||||
|
|
@ -3064,11 +3081,13 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
const VkComponentMapping components = ViewComponents(resource);
|
||||
const auto guestBytes = record != nullptr && record->decoded ? record->guestBytes : DescribeSurface(resource).guestBytes;
|
||||
std::shared_ptr<Texture> texture;
|
||||
if (record != nullptr && record->texture != nullptr) {
|
||||
const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element);
|
||||
if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture;
|
||||
if (texture == nullptr && record != nullptr && record->texture != nullptr) {
|
||||
texture = fastTexture(*record);
|
||||
(texture != nullptr ? counters.fastHits : counters.fastMisses).fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element));
|
||||
if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare);
|
||||
if (element < binding.imageUnnormalized.size() && binding.imageUnnormalized[element]) {
|
||||
const auto range = texture->SampledViewRange(firstLayer);
|
||||
const bool singleLevel = range.levels == 1u && range.layers == 1u && resource.baseLevel == 0u && EffectiveMinLod(resource) == 0.0f;
|
||||
|
|
@ -3085,6 +3104,11 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
(singleLevel ? source.singleLevelImage : source.mipmappedImage) = true;
|
||||
}
|
||||
}
|
||||
if (words.size() == 8) {
|
||||
std::copy(words.begin(), words.end(), previousSampled.words.begin());
|
||||
previousSampled.depthCompare = depthCompare;
|
||||
previousSampled.texture = texture;
|
||||
}
|
||||
textures.push_back(std::move(texture));
|
||||
textureFirstLayer.push_back(firstLayer);
|
||||
describedRanges.push_back({"texture", resource.baseAddress, guestBytes, resource.width, resource.height, resource.format, static_cast<int>(resource.tileMode), resource.dccAddress});
|
||||
|
|
@ -3094,6 +3118,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind
|
|||
reportTextureCounters();
|
||||
return;
|
||||
}
|
||||
previousSampled = {};
|
||||
// Consecutive identical storage descriptors address successive mips of one texture (dynamic-mip
|
||||
// storage writes).
|
||||
std::uint32_t mipOffset = 0;
|
||||
|
|
|
|||
|
|
@ -3763,6 +3763,24 @@ void imageMemoTests(const Device& device, Recorder& recorder) {
|
|||
Require(sample(first) == remade, "the remade texture is not the cache's");
|
||||
const auto replaced = sample(second);
|
||||
Require(replaced != other && sample(second) == replaced && sample(first) == remade, "the cache does not answer with the replacements");
|
||||
const auto firstWords = linearTextureWords(reinterpret_cast<std::uint64_t>(first), width, height, 56);
|
||||
const auto secondWords = linearTextureWords(reinterpret_cast<std::uint64_t>(second), width, height, 56);
|
||||
const auto bindElements = [&](std::initializer_list<std::array<std::uint32_t, 8>> elements, std::vector<bool> depthCompare) {
|
||||
program.bindings[0].count = static_cast<std::uint32_t>(elements.size());
|
||||
program.bindings[0].guestDescriptor.clear();
|
||||
for (const auto& words : elements) program.bindings[0].guestDescriptor.insert(program.bindings[0].guestDescriptor.end(), words.begin(), words.end());
|
||||
program.bindings[0].imageDepthCompare = std::move(depthCompare);
|
||||
program.bindings[0].imageSamplers.assign(elements.size(), 0u);
|
||||
ShaderResources resources(context, compiled);
|
||||
Require(resources.SampledTextures().size() == elements.size(), "the build has the wrong number of sampled textures");
|
||||
kept.insert(kept.end(), resources.SampledTextures().begin(), resources.SampledTextures().end());
|
||||
return resources.SampledTextures();
|
||||
};
|
||||
const auto repeated = bindElements({firstWords, firstWords, secondWords, firstWords}, {});
|
||||
Require(repeated[0] == remade && repeated[1] == remade && repeated[2] == replaced && repeated[3] == remade, "repeated elements of one build do not bind the cache's textures");
|
||||
program.bindings[0].count = 1;
|
||||
program.bindings[0].imageDepthCompare.clear();
|
||||
program.bindings[0].imageSamplers.assign(1, 0u);
|
||||
recorder.Submit();
|
||||
device.WaitQueue();
|
||||
recorder.Sync();
|
||||
|
|
|
|||
Loading…
Reference in New Issue