mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 21:36:54 +01:00
shader: bound waterfall image table keys converted from clamped float arithmetic
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
@@ -3014,6 +3015,18 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
// The EXEC predicate of the image access through use; empty for any other use.
|
||||
Value AccessPredicate(const Use& use, bool sampler) const {
|
||||
const auto& access = *use.user;
|
||||
if (use.operand != (sampler ? 1u : 0u) ||
|
||||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
||||
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
||||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
||||
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
||||
const auto mask = access.Arg(access.NumArgs() - 1u);
|
||||
return mask.GetType() == Type::U1 ? mask : Value {};
|
||||
}
|
||||
|
||||
bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor,
|
||||
IndirectDescriptorPlan& plan) {
|
||||
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
|
||||
@@ -3102,7 +3115,19 @@ private:
|
||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) &&
|
||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) &&
|
||||
!MatchReversedImageRange(key, indirect) && !MatchWorkgroupKey(key, indirect)) {
|
||||
const auto maximum = SelectorMaximum(key);
|
||||
auto maximum = SelectorMaximum(key);
|
||||
if (maximum >= MaxIndirectDescriptorProbes) {
|
||||
// Each image access bounds the key on the lanes it executes.
|
||||
maximum = 0u;
|
||||
for (const auto& use: handle.Uses()) {
|
||||
const auto active = AccessPredicate(use, false);
|
||||
if (active.IsEmpty()) {
|
||||
maximum = UINT32_MAX;
|
||||
break;
|
||||
}
|
||||
maximum = std::max(maximum, SelectorMaximum(key, {}, 0, active));
|
||||
}
|
||||
}
|
||||
if (maximum >= MaxIndirectDescriptorProbes) return false;
|
||||
indirect.key_count = Value(maximum + 1u);
|
||||
}
|
||||
@@ -3111,26 +3136,16 @@ private:
|
||||
// Keep the GPU selector and descriptor reads, and preserve the proven scalar
|
||||
// offset domain within the bounded image table.
|
||||
if (sampler || plan.split_offsets) return false;
|
||||
const auto active = [&](const Use& use, bool sampler) -> Value {
|
||||
const auto& access = *use.user;
|
||||
if (use.operand != (sampler ? 1u : 0u) ||
|
||||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
||||
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
||||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
||||
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
||||
const auto mask = access.Arg(access.NumArgs() - 1u);
|
||||
return mask.GetType() == Type::U1 ? mask : Value {};
|
||||
};
|
||||
bool shared_selector = false;
|
||||
if (!plan.table_indexed) {
|
||||
for (const auto& owner: m_indirect_descriptors) {
|
||||
const auto& selected = *m_sources[owner.source].indirect_descriptor;
|
||||
if (!selected.selector || !EquivalentValue(m_program, key, owner.key) ||
|
||||
!std::ranges::all_of(handle.Uses(), [&](const Use& target) {
|
||||
const auto target_active = active(target, false);
|
||||
const auto target_active = AccessPredicate(target, false);
|
||||
return !target_active.IsEmpty() &&
|
||||
std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) {
|
||||
const auto owner_active = active(witness,
|
||||
const auto owner_active = AccessPredicate(witness,
|
||||
owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource);
|
||||
return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) &&
|
||||
Implies(target_active, owner_active);
|
||||
@@ -3451,6 +3466,42 @@ private:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bounds of an F32 value on the lanes where guard holds; NaN when unknown.
|
||||
std::pair<float, float> FloatRange(Value value, Value guard, uint32_t depth = 0) const {
|
||||
constexpr std::pair unknown {NAN, NAN};
|
||||
value = ActiveValue(value, guard);
|
||||
if (value.IsImmediate() && value.GetType() == Type::F32)
|
||||
return {value.F32Value(), value.F32Value()};
|
||||
const auto* inst = value.TryInstruction();
|
||||
// Each multiply-add evaluates three operands; keep the search shallow.
|
||||
if (depth > 12u || inst == nullptr) return unknown;
|
||||
const auto range = [&](uint32_t arg) {
|
||||
return FloatRange(inst->Arg(arg), guard, depth + 1u);
|
||||
};
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::BitCastF32U32:
|
||||
case ValueOpcode::BitCastU32F32: return range(0);
|
||||
// Clamping maps NaN to zero.
|
||||
case ValueOpcode::FPSaturate32: return {0.0f, 1.0f};
|
||||
case ValueOpcode::FPFloor32: {
|
||||
const auto [low, high] = range(0);
|
||||
return {std::floor(low), std::floor(high)};
|
||||
}
|
||||
case ValueOpcode::FPMad32: {
|
||||
// The rounded multiply and add are monotonic, so the bounds lie at the corners.
|
||||
const auto a = range(0), b = range(1), c = range(2);
|
||||
if (!std::isfinite(a.first) || !std::isfinite(a.second) ||
|
||||
!std::isfinite(b.first) || !std::isfinite(b.second)) return unknown;
|
||||
const auto [low, high] = std::minmax({a.first * b.first, a.first * b.second,
|
||||
a.second * b.first, a.second * b.second});
|
||||
const std::pair bounds {low + c.first, high + c.second};
|
||||
if (!std::isfinite(bounds.first) || !std::isfinite(bounds.second)) return unknown;
|
||||
return bounds;
|
||||
}
|
||||
default: return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0,
|
||||
Value guard = {}) const {
|
||||
if (depth > 32u) return UINT32_MAX;
|
||||
@@ -3466,9 +3517,13 @@ private:
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
return SelectedSelectorDomain(*inst, guard).maximum;
|
||||
case ValueOpcode::ReadLane:
|
||||
// ReadLane ignores EXEC; only context-free bounds on every source lane apply.
|
||||
return PossibleU32Bits(inst->Arg(0));
|
||||
case ValueOpcode::ReadLane: {
|
||||
// ReadLane ignores EXEC; only context-free bounds on every source lane apply,
|
||||
// unless guard proves a waterfall key equal to the executing lanes' local value.
|
||||
const auto local = EqualLocalKey(guard, value);
|
||||
return local.IsEmpty() ? PossibleU32Bits(inst->Arg(0))
|
||||
: SelectorMaximum(local, bounds, depth + 1u, guard);
|
||||
}
|
||||
case ValueOpcode::Phi: {
|
||||
const auto initial = InitialCandidateMask(value);
|
||||
if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard);
|
||||
@@ -3495,6 +3550,18 @@ private:
|
||||
NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX;
|
||||
case ValueOpcode::UMin32:
|
||||
case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1));
|
||||
case ValueOpcode::SMin32: {
|
||||
// Signed and unsigned minimums agree on nonnegative operands.
|
||||
const auto left = maximum(0), right = maximum(1);
|
||||
if (std::max(left, right) <= INT32_MAX) return std::min(left, right);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::ConvertS32F32: {
|
||||
// The conversion truncates toward zero.
|
||||
const auto [low, high] = FloatRange(inst->Arg(0), guard);
|
||||
if (low >= 0.0f && high < 0x1p31f) return static_cast<uint32_t>(high);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::SelectU32:
|
||||
if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1);
|
||||
return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard)
|
||||
|
||||
Reference in New Issue
Block a user