mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
shader: preserve complete clear recognition with dispatch EXEC bounds
Co-authored-by: TheCruZ <thecruzwtf@gmail.com> Co-authored-by: Mac <me@itsmemac.com>
This commit is contained in:
@@ -708,11 +708,45 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveComputeBufferFill proves complete workgroups and matching requested extents, so every
|
||||||
|
// dispatch-bound predicate accepted here is true for every invocation covered by the fill.
|
||||||
|
static bool IsFullDispatchPredicate(Value value, uint32_t depth = 0) {
|
||||||
|
value = value.Resolve();
|
||||||
|
if (value == Value(true)) return true;
|
||||||
|
const auto* inst = value.TryInstruction();
|
||||||
|
if (inst == nullptr || depth > 32) return false;
|
||||||
|
if (inst->GetOpcode() == ValueOpcode::LogicalAnd) {
|
||||||
|
return IsFullDispatchPredicate(inst->Arg(0), depth + 1) &&
|
||||||
|
IsFullDispatchPredicate(inst->Arg(1), depth + 1);
|
||||||
|
}
|
||||||
|
if (inst->GetOpcode() != ValueOpcode::ULessThan32) return false;
|
||||||
|
const auto* global = inst->Arg(0).Resolve().TryInstruction();
|
||||||
|
const auto* extent = inst->Arg(1).Resolve().TryInstruction();
|
||||||
|
if (global == nullptr || global->GetOpcode() != ValueOpcode::GetBuiltin ||
|
||||||
|
global->Arg(0).Resolve() != Value(static_cast<uint32_t>(StageInputKind::GlobalInvocationId)) ||
|
||||||
|
extent == nullptr || extent->GetOpcode() != ValueOpcode::GetDispatchThreadExtent)
|
||||||
|
return false;
|
||||||
|
const auto axis = global->Arg(1).Resolve();
|
||||||
|
return axis.IsImmediate() && axis.U32() < 3 && extent->Arg(0).Resolve() == axis;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value FillValue(Value value, Value guard) {
|
||||||
|
for (uint32_t depth = 0; depth <= 32; ++depth) {
|
||||||
|
value = value.Resolve();
|
||||||
|
const auto* inst = value.TryInstruction();
|
||||||
|
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::SelectU32 ||
|
||||||
|
inst->Arg(0).Resolve() != guard)
|
||||||
|
return value;
|
||||||
|
value = inst->Arg(1);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// Nonnegative affine coefficients for constant, local and workgroup coordinates. Reject modular
|
// Nonnegative affine coefficients for constant, local and workgroup coordinates. Reject modular
|
||||||
// arithmetic that could wrap; runtime coverage also bounds the largest invocation index.
|
// arithmetic that could wrap; runtime coverage also bounds the largest invocation index.
|
||||||
static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t axis,
|
static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t axis, Value guard,
|
||||||
uint32_t depth = 0) {
|
uint32_t depth = 0) {
|
||||||
value = value.Resolve();
|
value = FillValue(value, guard);
|
||||||
if (depth > 32 || value.GetType() != Type::U32) {
|
if (depth > 32 || value.GetType() != Type::U32) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -736,8 +770,8 @@ static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t ax
|
|||||||
op != ValueOpcode::ShiftLeftLogical32) {
|
op != ValueOpcode::ShiftLeftLogical32) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto left = FillIndex(inst->Arg(0), axis, depth + 1);
|
auto left = FillIndex(inst->Arg(0), axis, guard, depth + 1);
|
||||||
auto right = FillIndex(inst->Arg(1), axis, depth + 1);
|
auto right = FillIndex(inst->Arg(1), axis, guard, depth + 1);
|
||||||
if (!left || !right) {
|
if (!left || !right) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -793,6 +827,7 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
UniformFillPlan result;
|
UniformFillPlan result;
|
||||||
result.fill.resource = memory.resource;
|
result.fill.resource = memory.resource;
|
||||||
Value data;
|
Value data;
|
||||||
|
Value guard(true);
|
||||||
if (store->GetOpcode() == ValueOpcode::ImageWrite) {
|
if (store->GetOpcode() == ValueOpcode::ImageWrite) {
|
||||||
if (program.info.images.size() != 1 || memory.dmask != 1 || memory.data_bits != 32 ||
|
if (program.info.images.size() != 1 || memory.dmask != 1 || memory.data_bits != 32 ||
|
||||||
memory.image_has_mip || memory.image_sample_flags != 0 || memory.image_r128 ||
|
memory.image_has_mip || memory.image_sample_flags != 0 || memory.image_r128 ||
|
||||||
@@ -803,7 +838,7 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
const auto* address = store->Arg(1).ResolveInstruction();
|
const auto* address = store->Arg(1).ResolveInstruction();
|
||||||
if (address == nullptr || address->GetOpcode() != ValueOpcode::MakeImageAddress) return {};
|
if (address == nullptr || address->GetOpcode() != ValueOpcode::MakeImageAddress) return {};
|
||||||
for (uint32_t axis = 0; axis < 3; ++axis) {
|
for (uint32_t axis = 0; axis < 3; ++axis) {
|
||||||
const auto index = FillIndex(address->Arg(axis), axis);
|
const auto index = FillIndex(address->Arg(axis), axis, guard);
|
||||||
if (!index || (*index)[0] != 0) return {};
|
if (!index || (*index)[0] != 0) return {};
|
||||||
if (axis < 2) {
|
if (axis < 2) {
|
||||||
if ((*index)[1] != 1 || (*index)[2] == 0) return {};
|
if ((*index)[1] != 1 || (*index)[2] == 0) return {};
|
||||||
@@ -825,20 +860,22 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
ValueOpcode::StoreBufferU32x3, ValueOpcode::StoreBufferU32x4};
|
ValueOpcode::StoreBufferU32x3, ValueOpcode::StoreBufferU32x4};
|
||||||
const auto store_op = std::ranges::find(stores, op);
|
const auto store_op = std::ranges::find(stores, op);
|
||||||
if (store_op == stores.end() || store->Arg(2).Resolve() != Value(0u) ||
|
if (store_op == stores.end() || store->Arg(2).Resolve() != Value(0u) ||
|
||||||
store->Arg(3).Resolve() != Value(0u) || store->Arg(5).Resolve() != Value(true))
|
store->Arg(3).Resolve() != Value(0u))
|
||||||
return {};
|
return {};
|
||||||
|
guard = store->Arg(5).Resolve();
|
||||||
|
if (!IsFullDispatchPredicate(guard)) return {};
|
||||||
if (!memory.formatted || memory.typed || !memory.idxen || memory.offen || memory.offset != 0 ||
|
if (!memory.formatted || memory.typed || !memory.idxen || memory.offen || memory.offset != 0 ||
|
||||||
memory.data_bits != 32 ||
|
memory.data_bits != 32 ||
|
||||||
memory.data_dwords != static_cast<uint32_t>(store_op - stores.begin() + 1))
|
memory.data_dwords != static_cast<uint32_t>(store_op - stores.begin() + 1))
|
||||||
return {};
|
return {};
|
||||||
const auto address = FillIndex(store->Arg(1), 0);
|
const auto address = FillIndex(store->Arg(1), 0, guard);
|
||||||
if (!address || (*address)[0] != 0 || (*address)[1] != 1 || (*address)[2] == 0) return {};
|
if (!address || (*address)[0] != 0 || (*address)[1] != 1 || (*address)[2] == 0) return {};
|
||||||
result.fill.kind = UniformFillKind::Buffer;
|
result.fill.kind = UniformFillKind::Buffer;
|
||||||
result.fill.group_stride[0] = static_cast<uint32_t>((*address)[2]);
|
result.fill.group_stride[0] = static_cast<uint32_t>((*address)[2]);
|
||||||
result.fill.words = memory.data_dwords;
|
result.fill.words = memory.data_dwords;
|
||||||
data = store->Arg(4);
|
data = store->Arg(4);
|
||||||
}
|
}
|
||||||
data = data.Resolve();
|
data = FillValue(data, guard);
|
||||||
const auto* vector = data.TryInstruction();
|
const auto* vector = data.TryInstruction();
|
||||||
constexpr std::array composites {ValueOpcode::CompositeConstructU32x2,
|
constexpr std::array composites {ValueOpcode::CompositeConstructU32x2,
|
||||||
ValueOpcode::CompositeConstructU32x3,
|
ValueOpcode::CompositeConstructU32x3,
|
||||||
@@ -847,7 +884,7 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
(vector == nullptr || vector->GetOpcode() != composites[result.fill.words - 2]))
|
(vector == nullptr || vector->GetOpcode() != composites[result.fill.words - 2]))
|
||||||
return {};
|
return {};
|
||||||
for (uint32_t i = 0; i < result.fill.words; ++i) {
|
for (uint32_t i = 0; i < result.fill.words; ++i) {
|
||||||
const auto word = result.fill.words == 1 ? data : vector->Arg(i);
|
const auto word = FillValue(result.fill.words == 1 ? data : vector->Arg(i), guard);
|
||||||
if (word.GetType() != Type::U32 ||
|
if (word.GetType() != Type::U32 ||
|
||||||
!ValidateRuntimeValue(program, word, RuntimeValueType::Integer))
|
!ValidateRuntimeValue(program, word, RuntimeValueType::Integer))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -33173,6 +33173,15 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
|||||||
packed_clear == 0xff000000u,
|
packed_clear == 0xff000000u,
|
||||||
"exact Prospero runtime binding did not resolve to a complete clear");
|
"exact Prospero runtime binding did not resolve to a complete clear");
|
||||||
|
|
||||||
|
auto narrow_exec = code;
|
||||||
|
narrow_exec.insert(narrow_exec.end() - 3,
|
||||||
|
EncodeSop1(0x24u, 10, InlineU32(1))); // S_AND_SAVEEXEC_B64.
|
||||||
|
runtime_resources = Compile(narrow_exec);
|
||||||
|
Require("Ps5GameExampleImageClear", "guest EXEC narrowing",
|
||||||
|
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
|
||||||
|
packed_clear, size),
|
||||||
|
"a guest predicate narrowing the store was accepted as a complete clear");
|
||||||
|
|
||||||
auto wrong_index = code;
|
auto wrong_index = code;
|
||||||
wrong_index[0] = (wrong_index[0] & ~(0x3ffu << 16u)) | (0x347u << 16u);
|
wrong_index[0] = (wrong_index[0] & ~(0x3ffu << 16u)) | (0x347u << 16u);
|
||||||
runtime_resources = Compile(wrong_index);
|
runtime_resources = Compile(wrong_index);
|
||||||
@@ -33188,6 +33197,18 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
|
|||||||
packed_clear, size),
|
packed_clear, size),
|
||||||
"non-uniform uint4 data was replaced with a color clear");
|
"non-uniform uint4 data was replaced with a color clear");
|
||||||
runtime_resources = positive;
|
runtime_resources = positive;
|
||||||
|
compute.dispatch_threads_num[0] = 63;
|
||||||
|
Require("Ps5GameExampleImageClear", "mismatched requested extent",
|
||||||
|
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
|
||||||
|
packed_clear, size),
|
||||||
|
"a dispatch exceeding its requested extent was classified as a complete clear");
|
||||||
|
runtime_resources.buffers[0].dwords[2] = 65;
|
||||||
|
compute.dispatch_threads_num[0] = 65;
|
||||||
|
Require("Ps5GameExampleImageClear", "partial final workgroup",
|
||||||
|
!ResolveComputeBufferFill(compute, 65, 1, 1, 0x61u, descriptor,
|
||||||
|
packed_clear, size),
|
||||||
|
"a nondivisible thread dispatch was classified as a complete clear");
|
||||||
|
runtime_resources = positive;
|
||||||
compute.dispatch_threads_num[0] = 32;
|
compute.dispatch_threads_num[0] = 32;
|
||||||
Require("Ps5GameExampleImageClear", "partial dispatch",
|
Require("Ps5GameExampleImageClear", "partial dispatch",
|
||||||
!ResolveComputeBufferFill(compute, 32, 1, 1, 0x61u, descriptor,
|
!ResolveComputeBufferFill(compute, 32, 1, 1, 0x61u, descriptor,
|
||||||
|
|||||||
Reference in New Issue
Block a user