mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 05:46:56 +01:00
shader: read bit-scan records only at ranks their mask buffer words set
This commit is contained in:
@@ -157,7 +157,9 @@ KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
||||
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||
A, B, C, D, E, F, G, H, I, J, K)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
|
||||
l)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p,
|
||||
q, r)
|
||||
|
||||
@@ -572,6 +572,13 @@ struct DescriptorSource {
|
||||
|
||||
bool operator==(const CountedRows& other) const = default;
|
||||
};
|
||||
struct MaskRanks {
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t stride = 0; // Byte spacing of the mask words its load can address.
|
||||
uint32_t offset = 0;
|
||||
|
||||
bool operator==(const MaskRanks& other) const = default;
|
||||
};
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t stride = 0;
|
||||
uint32_t offset = 0;
|
||||
@@ -579,11 +586,12 @@ struct DescriptorSource {
|
||||
uint32_t bit_offset = 0;
|
||||
uint32_t bit_mask = UINT32_MAX;
|
||||
bool global = false;
|
||||
// Four DWORD bitmap words per record; set-bit ranks subtract from the last key.
|
||||
// Four or eight DWORD bitmap words per record; set ranks subtract from the last key.
|
||||
bool bitset = false;
|
||||
std::optional<CountedRows> rows;
|
||||
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
|
||||
std::optional<uint32_t> excluded; // A guard keeps this key from the read.
|
||||
std::optional<MaskRanks> ranks; // Records are the bits some mask word sets.
|
||||
|
||||
bool operator==(const SelectorRead& other) const = default;
|
||||
};
|
||||
|
||||
@@ -331,6 +331,18 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
||||
keys.resize(count);
|
||||
return true;
|
||||
};
|
||||
// Reads the key of each record whose rank is set.
|
||||
const auto read_ranks = [&](uint64_t base, uint64_t size, uint64_t first, uint64_t step,
|
||||
uint32_t ranks) {
|
||||
keys.reserve(std::popcount(ranks));
|
||||
for (; ranks != 0u; ranks &= ranks - 1u) {
|
||||
const auto offset = first + std::countr_zero(ranks) * step;
|
||||
uint32_t key = 0;
|
||||
if (offset > UINT32_MAX || !read_selector(base, size, offset, {&key, 1})) return false;
|
||||
keys.push_back(key);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (selector->rows) {
|
||||
const auto& rows = *selector->rows;
|
||||
DescriptorValue count_value;
|
||||
@@ -450,8 +462,32 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
||||
// Out-of-bounds scalar reads return zero.
|
||||
const auto end = count != 0u ? last * step + selector->offset + 4u : 0u;
|
||||
if (end > uint64_t {UINT32_MAX} + 1u) return false;
|
||||
if (!read_keys(base, size, uint64_t {first} * step + selector->offset, step, count))
|
||||
const auto start = uint64_t {first} * step + selector->offset;
|
||||
if (selector->ranks) {
|
||||
// A scan selects the ranks some word of its mask's address class sets.
|
||||
const auto& mask = *selector->ranks;
|
||||
DescriptorValue mask_value;
|
||||
ShaderBufferResource masks;
|
||||
if (count > 32u || !WalkedDescriptor(program, mask.source, mask_value) ||
|
||||
!DecodeBufferDescriptor(mask_value, masks)) return false;
|
||||
// Each chunk holds whole strides of the power-of-two class, or one of its words.
|
||||
const auto words = mask.stride / 4u;
|
||||
const auto chunk =
|
||||
words <= MaxIndirectDescriptorProbes ? MaxIndirectDescriptorProbes : 1u;
|
||||
uint32_t ranks = 0;
|
||||
for (auto at = uint64_t {mask.offset}; at + 4u <= masks.GetSize();
|
||||
at += uint64_t {std::max(chunk, words)} * 4u) {
|
||||
keys.resize(std::min<uint64_t>((masks.GetSize() - at) / 4u, chunk));
|
||||
if (!ReadScalarTable(masks.Base48(), masks.GetSize(), at, runtime, keys))
|
||||
return false;
|
||||
for (size_t word = 0; word < keys.size(); word += words) ranks |= keys[word];
|
||||
}
|
||||
keys.clear();
|
||||
if (count < 32u) ranks &= (1u << count) - 1u;
|
||||
if (!read_ranks(base, size, start, step, ranks)) return false;
|
||||
} else if (!read_keys(base, size, start, step, count)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!indirect.selector_mask.IsEmpty()) {
|
||||
uint32_t mask = 0;
|
||||
uint32_t count = 0;
|
||||
@@ -463,18 +499,8 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
||||
if (count < 32u) mask &= (1u << count) - 1u;
|
||||
const auto material_base =
|
||||
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
|
||||
keys.reserve(std::popcount(mask));
|
||||
while (mask != 0u) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto offset = static_cast<uint64_t>(selector->offset) +
|
||||
static_cast<uint64_t>(index) * selector->stride;
|
||||
if (offset > UINT32_MAX) return false;
|
||||
uint32_t key = 0;
|
||||
if (!read_selector(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
|
||||
{&key, 1})) return false;
|
||||
keys.push_back(key);
|
||||
mask &= mask - 1u;
|
||||
}
|
||||
if (!read_ranks(material_base, UINT64_MAX, selector->offset, selector->stride, mask))
|
||||
return false;
|
||||
} else if (selector->stride == 0u) {
|
||||
ShaderBufferResource material;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
|
||||
@@ -2382,8 +2382,10 @@ private:
|
||||
if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) {
|
||||
count = bound->Arg(1);
|
||||
} else if (workgroup_axis == UINT32_MAX && !require_bound) {
|
||||
// Other bounded indices, such as bit scans, read the records up to their maximum.
|
||||
// Other bounded indices, such as bit scans, read the records up to their maximum;
|
||||
// a bit scan of a nonzero mask load reads only the ranks its words set.
|
||||
const auto maximum = SelectorMaximum(index);
|
||||
if (maximum == 31u) selector.ranks = BitScanRanks(index);
|
||||
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
|
||||
}
|
||||
// A record selected from a shared append list is below the list's bound.
|
||||
@@ -2580,8 +2582,10 @@ private:
|
||||
bool all = true;
|
||||
std::vector<size_t> terms;
|
||||
};
|
||||
const auto load_guard = reads[0]->Arg(4).Resolve();
|
||||
const auto words = static_cast<uint32_t>(reads.size()) * 4u;
|
||||
// A scalar load reads one word without EXEC; four-word loads take their guard.
|
||||
const bool scalar = reads[0]->GetOpcode() == ValueOpcode::ReadConstBuffer;
|
||||
const auto load_guard = scalar ? Value(true) : reads[0]->Arg(4).Resolve();
|
||||
const auto words = static_cast<uint32_t>(reads.size()) * (scalar ? 1u : 4u);
|
||||
std::vector<State> states(2);
|
||||
std::vector<Equation> equations(2);
|
||||
const auto add = [&](State state) {
|
||||
@@ -2649,6 +2653,9 @@ private:
|
||||
equation.terms = {1u};
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::ReadConstBuffer:
|
||||
if (scalar && inst == reads[state.component]) equation.terms = {1u};
|
||||
break;
|
||||
case ValueOpcode::Phi:
|
||||
// The predecessor may be an earlier outer iteration. Establish
|
||||
// this register's invariant without the current word or EXEC.
|
||||
@@ -2717,6 +2724,52 @@ private:
|
||||
return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; });
|
||||
}
|
||||
|
||||
// The mask words whose set bits are every record a bit scan of index selects: the scanned
|
||||
// value keeps only bits of one scalar load, which reads a word its address can reach.
|
||||
std::optional<DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks>
|
||||
BitScanRanks(Value index) {
|
||||
const auto* scan = index.Resolve().TryInstruction();
|
||||
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindILsb32) return {};
|
||||
const auto* masked = scan->Arg(0).Resolve().TryInstruction();
|
||||
if (masked == nullptr) return {};
|
||||
std::vector<const Inst*> values {masked};
|
||||
const Inst* load = nullptr;
|
||||
for (size_t i = 0; i < values.size() && load == nullptr; ++i) {
|
||||
const auto* value = values[i];
|
||||
if (value->GetOpcode() == ValueOpcode::ReadConstBuffer) load = value;
|
||||
if (value->GetOpcode() != ValueOpcode::Phi &&
|
||||
value->GetOpcode() != ValueOpcode::BitwiseAnd32) continue;
|
||||
for (size_t arg = 0; arg < value->NumArgs(); ++arg) {
|
||||
const auto* next = value->Arg(arg).Resolve().TryInstruction();
|
||||
if (next != nullptr && std::ranges::find(values, next) == values.end())
|
||||
values.push_back(next);
|
||||
}
|
||||
}
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = load != nullptr ? ScalarReadMemory(*load, memory_index) : nullptr;
|
||||
if (memory == nullptr || memory->offset > INT32_MAX ||
|
||||
!MemoryIndexBelongsTo(memory_index, *load) ||
|
||||
!BitsetMaskSubset(scan->Arg(0), Value(true), Value(0u), std::array {load})) return {};
|
||||
auto address = load->Arg(1);
|
||||
auto active = Value(true);
|
||||
const auto* lane = address.Resolve().TryInstruction();
|
||||
if (lane != nullptr && lane->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||
if (!HasActiveLane(lane->Arg(1), lane->Parent())) return {};
|
||||
active = lane->Arg(1);
|
||||
address = lane->Arg(0);
|
||||
}
|
||||
// A left shift keeps the address a multiple of its scale; other addresses read any word.
|
||||
const auto* shift = ActiveValue(address, active).TryInstruction();
|
||||
uint32_t bits = 0;
|
||||
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
|
||||
!ImmediateU32(shift->Arg(1), bits) || bits >= 32u) bits = 0;
|
||||
DescriptorSource source;
|
||||
if (!MakeRuntimeTableSource(*load, source)) return {};
|
||||
return DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks {
|
||||
.source = InternSource(source), .stride = std::max(1u << bits, 4u),
|
||||
.offset = memory->offset & ~3u};
|
||||
}
|
||||
|
||||
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds, uint32_t words,
|
||||
DescriptorSource::IndirectDescriptor& indirect) {
|
||||
const auto selected_value = [&](Value value) {
|
||||
|
||||
Reference in New Issue
Block a user