shader: execute PS5 BVH intersections and local FLAT stacks

Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
Co-authored-by: Mac <me@itsmemac.com>
Co-authored-by: Senaxx <michelbeld@gmail.com>
Co-authored-by: Jean-Philippe <github@meunier.jp>
This commit is contained in:
nmzik
2026-10-01 07:55:28 +04:00
parent 581dad7dd3
commit 337f432016
22 changed files with 859 additions and 156 deletions
@@ -220,7 +220,6 @@ struct PipelineCache::ProgramCache {
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
std::vector<Permutation> permutations;
bool skip_dispatch = false;
};
struct ProgramKeyHash {
@@ -288,9 +287,6 @@ struct PipelineCache::ProgramCache {
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
BuildStageStaticKey(input_info, lookup_key.static_state);
auto entry = programs.find(lookup_key);
if (entry != programs.end() && entry->second.skip_dispatch) {
return {};
}
const ShaderRecompiler::IR::SrtRuntime runtime {
.user_data = user_data,
.shader_base = params.Base(),
@@ -358,11 +354,6 @@ struct PipelineCache::ProgramCache {
}
DumpShaderOriginal(stage_name, options.shader_hash, params.code);
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
if (translated.skip_dispatch) {
entry = programs.try_emplace(lookup_key, ShaderRecompiler::IR::ResourcePlan {}).first;
entry->second.skip_dispatch = true;
return {};
}
if (entry == programs.end()) {
entry = programs.try_emplace(lookup_key,
ShaderRecompiler::IR::ExtractResourcePlan(translated.program)).first;
@@ -256,10 +256,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
input_info.dispatch_thread_dimensions = use_thread_dimensions;
const auto compute_program =
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
if (use_thread_dimensions) {
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
@@ -417,10 +413,6 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
ShaderComputeInputInfo input_info {};
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
if (!compute_program) {
// Temporary until RT is implemented.
return;
}
buffer.EndRendering();
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto& bindings = m_compute_bindings;
@@ -19,7 +19,6 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <fmt/format.h>
#include <map>
@@ -523,20 +522,6 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(decoded.instructions.size()), phase_ms());
// Temporary workaround for games that compile ray-tracing shaders before
// the player can select a mode without ray tracing.
if (options.stage == ShaderType::Compute && decoded.has_bvh) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
const auto& bvh = decoded.instructions.back();
Log::WriteToConsoleAndLog(fmt::format(
"Warning: ray tracing is not implemented; skipping compute dispatches containing "
"BVH intersection instructions (shader=0x{:016x}, pc=0x{:08x}, opcode=0x{:02x}).\n",
options.shader_hash, bvh.pc, bvh.opcode_id));
}
return {.skip_dispatch = true};
}
std::string decoded_dump;
if (options.dump_ir) {
decoded_dump = Decoder::ProgramToString(decoded);
@@ -645,7 +630,6 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
const IR::ResourceSpecialization& specialization,
uint32_t push_data_start_dword) {
EXIT_IF(translated.skip_dispatch);
const auto emit_begin = std::chrono::steady_clock::now();
auto& ir = translated.program;
IR::ApplyResourceSpecialization(ir, specialization);
@@ -28,7 +28,6 @@ struct TranslateResult {
IR::Program program;
std::string decoded_dump;
std::string cfg_dump;
bool skip_dispatch = false;
};
struct CompileResult {
@@ -122,6 +122,16 @@ void ValidateNativeProgram(const IR::Program& program) {
program.memory_info[index].kind == IR::ResourceKind::IndirectBuffer;
});
};
const auto local_flat_handle = [&](const IR::Inst& handle) {
return !handle.Uses().empty() &&
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
if (IR::AddressOpcodeInfoOf(use.user->GetOpcode()).access == IR::AddressAccess::None)
return false;
const auto index = use.user->Flags<IR::MemoryFlags>().index;
return index < program.memory_info.size() &&
program.memory_info[index].kind == IR::ResourceKind::FlatLocal;
});
};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto dense = inst.Flags<uint32_t>();
@@ -138,7 +148,7 @@ void ValidateNativeProgram(const IR::Program& program) {
if (planning_only_handle(inst)) {
break;
}
if (inst.NumArgs() != 2 || !program.info.uses_dma) {
if (inst.NumArgs() != 2 || (!program.info.uses_dma && !local_flat_handle(inst))) {
Fail(program, "typed address handle has invalid DMA metadata");
}
break;
@@ -188,11 +198,16 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
if (memory_index >= program.memory_info.size()) {
Fail(program, "address operation has invalid memory metadata");
}
if (program.memory_info[memory_index].kind == IR::ResourceKind::Scratch) {
const auto kind = program.memory_info[memory_index].kind;
if (kind == IR::ResourceKind::Scratch || kind == IR::ResourceKind::FlatLocal) {
if (program.scratch_dwords == 0) {
Fail(program, "scratch operation has no per-thread storage");
}
requirements.function_scratch = true;
if (kind == IR::ResourceKind::FlatLocal && program.stage != ShaderType::Compute &&
program.stage != ShaderType::Mesh) {
requirements.function_lds = true;
}
} else if (address_access == IR::AddressAccess::Write) {
Fail(program, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
}
@@ -247,6 +262,7 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
}
}
switch (inst.GetOpcode()) {
case IR::ValueOpcode::BvhIntersect: requirements.bvh = true; break;
case IR::ValueOpcode::Ballot: requirements.subgroup_ballot = true; break;
case IR::ValueOpcode::DppMoveU32:
case IR::ValueOpcode::ReadFirstLane:
@@ -0,0 +1,348 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
using Vec3 = std::array<uint32_t, 3>;
uint32_t Extract(EmitterState& s, uint32_t type, uint32_t vector, uint32_t index) {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeExtract, type, result, vector, index);
return result;
}
uint32_t Vector(EmitterState& s, uint32_t type, std::span<const uint32_t> components) {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeConstruct, type, result, components);
return result;
}
uint32_t NodeWord(EmitterState& s, uint32_t block, uint32_t word) {
const auto offset = Binary(s, spv::OpShiftLeftLogical, TypeU32(s), word, ConstantU32(s, 2));
const auto address = Binary(s, spv::OpIAdd, TypeScalarU64(s), block,
Unary(s, spv::OpUConvert, TypeScalarU64(s), offset));
const auto pointer = Unary(s, spv::OpConvertUToPtr, TypePhysicalU32Pointer(s), address);
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpLoad, TypeU32(s), result, pointer, spv::MemoryAccessAlignedMask, 4u);
return result;
}
uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
Vec3 origin, Vec3 direction) {
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
const auto zero = ConstantF32Value(s, 0.0f);
const auto op = [&](spv::Op code, uint32_t x, uint32_t y) {
const auto value = Binary(s, code, f, x, y);
s.builder.AddAnnotation(spv::OpDecorate, value, spv::DecorationNoContraction);
return value;
};
const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); };
const auto either = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); };
const auto both = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); };
const auto abs = [&](uint32_t x) { return EmitGlsl<GLSLstd450FAbs, IR::Type::F32>(s, x); };
const auto is_kind = [&](uint32_t n) { return cmp(spv::OpIEqual, kind, ConstantU32(s, n)); };
// The four triangles are (0,1,2), (1,3,2), (2,3,4), (2,4,0).
const std::array vertices {
EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s, kind, ConstantU32(s, 2)),
Select(s, u, is_kind(0), ConstantU32(s, 1),
Select(s, u, is_kind(3), ConstantU32(s, 4), ConstantU32(s, 3))),
Select(s, u, is_kind(2), ConstantU32(s, 4),
Select(s, u, is_kind(3), ConstantU32(s, 0), ConstantU32(s, 2))) };
std::array<Vec3, 3> positions;
for (uint32_t i = 0; i < 3; ++i) {
const auto first = Binary(s, spv::OpIMul, u, vertices[i], ConstantU32(s, 3));
for (uint32_t axis = 0; axis < 3; ++axis)
positions[i][axis] = Unary(s, spv::OpBitcast, f,
NodeWord(s, block, Binary(s, spv::OpIAdd, u, first, ConstantU32(s, axis))));
}
const auto flag = NodeWord(s, block, ConstantU32(s, 15));
const auto y_largest = cmp(spv::OpFOrdLessThan, abs(direction[0]), abs(direction[1]));
const auto z_largest = cmp(spv::OpFOrdLessThan,
Select(s, f, y_largest, abs(direction[1]), abs(direction[0])), abs(direction[2]));
const auto rotate = [&](Vec3 value) {
Vec3 result;
for (uint32_t i = 0; i < 3; ++i)
result[i] = Select(s, f, z_largest, value[i],
Select(s, f, y_largest, value[(i + 2) % 3], value[(i + 1) % 3]));
return result;
};
origin = rotate(origin);
direction = rotate(direction);
std::array<Vec3, 3> projected;
for (uint32_t i = 0; i < 3; ++i) {
const auto position = rotate(positions[i]);
Vec3 relative;
for (uint32_t axis = 0; axis < 3; ++axis)
relative[axis] = op(spv::OpFSub, position[axis], origin[axis]);
projected[i] = {op(spv::OpFSub, op(spv::OpFMul, relative[0], direction[2]),
op(spv::OpFMul, direction[0], relative[2])),
op(spv::OpFSub, op(spv::OpFMul, relative[1], direction[2]),
op(spv::OpFMul, direction[1], relative[2])), relative[2]};
}
Vec3 edge, weighted;
for (uint32_t i = 0; i < 3; ++i) {
const auto& first = projected[(i + 1) % 3];
const auto& second = projected[(i + 2) % 3];
edge[i] = op(spv::OpFSub, op(spv::OpFMul, second[0], first[1]),
op(spv::OpFMul, second[1], first[0]));
weighted[i] = op(spv::OpFMul, edge[i], direction[2]);
}
auto numerator = op(spv::OpFAdd, op(spv::OpFAdd,
op(spv::OpFMul, edge[0], projected[0][2]), op(spv::OpFMul, edge[1], projected[1][2])),
op(spv::OpFMul, edge[2], projected[2][2]));
auto denominator = op(spv::OpFAdd, op(spv::OpFAdd, weighted[0], weighted[1]), weighted[2]);
uint32_t negative = ConstantBool(s, false), positive = negative;
for (auto value: edge) {
negative = either(negative, cmp(spv::OpFOrdLessThan, value, zero));
positive = either(positive, cmp(spv::OpFOrdGreaterThan, value, zero));
}
const auto winding = cmp(spv::OpFOrdGreaterThan, denominator, zero);
auto missed = either(both(negative, positive), cmp(spv::OpFOrdEqual, denominator, zero));
missed = either(missed, Unary(s, spv::OpIsNan, b, numerator));
missed = either(missed, cmp(spv::OpFOrdLessThan,
Select(s, f, winding, numerator, Unary(s, spv::OpFNegate, f, numerator)), zero));
for (uint32_t i = 0; i < 3; ++i) {
const auto first = projected[(i + 1) % 3][1], second = projected[(i + 2) % 3][1];
const auto first_zero = cmp(spv::OpFOrdEqual, first, zero);
const auto horizontal = both(first_zero, cmp(spv::OpFOrdEqual, second, zero));
const auto below = either(cmp(spv::OpFOrdLessThan, first, zero),
both(first_zero, cmp(spv::OpFOrdGreaterThan, second, zero)));
const auto right = Binary(s, spv::OpLogicalNotEqual, b, below, winding);
const auto excluded = Select(s, b, horizontal,
cmp(spv::OpFOrdGreaterThan, projected[i][1], zero), right);
missed = either(missed, both(cmp(spv::OpFOrdEqual, edge[i], zero), excluded));
}
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag,
Binary(s, spv::OpIMul, u, kind, ConstantU32(s, 8)));
const auto coordinate = [&](uint32_t shift) {
const auto index = Binary(s, spv::OpBitwiseAnd, u,
Binary(s, spv::OpShiftRightLogical, u, remap, ConstantU32(s, shift)), ConstantU32(s, 3));
return Unary(s, spv::OpBitcast, u, Select(s, f,
cmp(spv::OpIEqual, index, ConstantU32(s, 1)), weighted[1],
Select(s, f, cmp(spv::OpIEqual, index, ConstantU32(s, 2)), weighted[2], weighted[0])));
};
const std::array result {
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32(s, 0x7f800000), numerator)),
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32Value(s, 1.0f), denominator)),
Select(s, u, bary, coordinate(0), Binary(s, spv::OpIAdd, u, flag, kind)),
Select(s, u, bary, coordinate(2), Select(s, u, missed, ConstantU32(s, 0), ConstantU32(s, 1))) };
return Vector(s, TypeU32Vector(s, 4), result);
}
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
uint32_t half, uint32_t child) {
const auto f = TypeF32(s);
const auto half_label = s.builder.AllocateId(), full_label = s.builder.AllocateId();
const auto merge = s.builder.AllocateId();
s.builder.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
s.builder.AddFunction(spv::OpBranchConditional, half, half_label, full_label);
EmitLabel(s, half_label);
std::array<uint32_t, 6> packed, full, result;
for (uint32_t pair = 0; pair < 3; ++pair) {
const auto word = NodeWord(s, first, ConstantU32(s, 4 + child * 3 + pair));
const auto unpacked = EmitGlsl<GLSLstd450UnpackHalf2x16, IR::Type::F32x2>(s, word);
packed[pair * 2] = Extract(s, f, unpacked, 0);
packed[pair * 2 + 1] = Extract(s, f, unpacked, 1);
}
s.builder.AddFunction(spv::OpBranch, merge);
EmitLabel(s, full_label);
for (uint32_t component = 0; component < 6; ++component) {
const auto word = 4 + child * 6 + component;
full[component] = Unary(s, spv::OpBitcast, f,
NodeWord(s, word < 16 ? first : second, ConstantU32(s, word % 16)));
}
s.builder.AddFunction(spv::OpBranch, merge);
EmitLabel(s, merge);
for (uint32_t component = 0; component < 6; ++component) {
result[component] = s.builder.AllocateId();
s.builder.AddFunction(spv::OpPhi, f, result[component], packed[component], half_label,
full[component], full_label);
}
return result;
}
uint32_t Boxes(EmitterState& s, uint32_t first, uint32_t second, uint32_t half,
uint32_t grow, uint32_t sort, uint32_t extent, const Vec3& origin, const Vec3& inverse) {
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
const auto zero = ConstantF32Value(s, 0.0f);
std::array<uint32_t, 4> depths, indices;
Vec3 forward;
for (uint32_t axis = 0; axis < 3; ++axis)
forward[axis] = Binary(s, spv::OpFOrdGreaterThanEqual, b, inverse[axis], zero);
for (uint32_t child = 0; child < 4; ++child) {
const auto box = Box(s, first, second, half, child);
Vec3 near, far;
for (uint32_t axis = 0; axis < 3; ++axis) {
const auto lo = Binary(s, spv::OpFMul, f,
Binary(s, spv::OpFSub, f, box[axis], origin[axis]), inverse[axis]);
const auto hi = Binary(s, spv::OpFMul, f,
Binary(s, spv::OpFSub, f, box[axis + 3], origin[axis]), inverse[axis]);
near[axis] = Select(s, f, forward[axis], lo, hi);
far[axis] = Select(s, f, forward[axis], hi, lo);
}
const auto entry = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(s,
EmitGlsl<GLSLstd450FMax, IR::Type::F32>(s, near[0], near[1]), near[2]);
const auto exit = EmitGlsl<GLSLstd450FMin, IR::Type::F32>(s,
EmitGlsl<GLSLstd450FMin, IR::Type::F32>(s, far[0], far[1]), far[2]);
// Valid hits have nonnegative exit distance. Saturate ULP growth at +infinity.
const auto bits = Unary(s, spv::OpBitcast, u, Select(s, f,
Binary(s, spv::OpFOrdEqual, b, exit, zero), zero, exit));
const auto grown_bits = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
Binary(s, spv::OpIAdd, u, bits, grow), ConstantU32(s, 0x7f800000));
const auto grown = Unary(s, spv::OpBitcast, f, grown_bits);
auto hit = Binary(s, spv::OpLogicalAnd, b,
Binary(s, spv::OpFOrdGreaterThanEqual, b, exit, zero),
Binary(s, spv::OpFOrdLessThan, b, entry, extent));
hit = Binary(s, spv::OpLogicalAnd, b, hit,
Binary(s, spv::OpFOrdLessThanEqual, b, entry, grown));
depths[child] = Unary(s, spv::OpBitcast, u, Select(s, f, hit,
Select(s, f, Binary(s, spv::OpFOrdGreaterThan, b, entry, zero), entry, zero),
ConstantF32(s, 0x7f800000)));
indices[child] = Select(s, u, hit, NodeWord(s, first, ConstantU32(s, child)), ConstantU32(s, ~0u));
}
// A select network keeps the original child order when sorting is disabled.
constexpr std::array<std::pair<uint32_t, uint32_t>, 5> network {{{0,1}, {2,3}, {0,2}, {1,3}, {1,2}}};
for (size_t step = 0; step < network.size(); ++step) {
const auto [a, c] = network[step];
const auto swap = Binary(s, spv::OpLogicalAnd, b, sort,
Binary(s, spv::OpUGreaterThan, b, depths[a], depths[c]));
const auto depth = depths[a], index = indices[a];
const auto later = [&](uint32_t position) {
return std::ranges::any_of(std::span(network).subspan(step + 1), [=](auto pair) {
return pair.first == position || pair.second == position;
});
};
if (later(a)) depths[a] = Select(s, u, swap, depths[c], depth);
if (later(c)) depths[c] = Select(s, u, swap, depth, depths[c]);
indices[a] = Select(s, u, swap, indices[c], index);
indices[c] = Select(s, u, swap, index, indices[c]);
}
return Vector(s, TypeU32Vector(s, 4), indices);
}
} // namespace
void DefineBvhIntersect(EmitterState& s) {
if (!s.requirements.bvh) return;
const auto u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), wide = TypeScalarU64(s);
const auto vec4 = TypeU32Vector(s, 4), vec3 = TypeF32Vector(s, 3);
const auto signature = s.builder.Type(spv::OpTypeFunction, vec4, vec4, u, f, vec3, vec3, vec3);
s.bvh_intersect_function = s.builder.AllocateId();
s.builder.AddName(s.bvh_intersect_function, "bvh_intersect");
s.builder.AddFunction(spv::OpFunction, vec4, s.bvh_intersect_function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 6> args;
const std::array types {vec4, u, f, vec3, vec3, vec3};
for (uint32_t i = 0; i < args.size(); ++i) {
args[i] = s.builder.AllocateId();
s.builder.AddFunction(spv::OpFunctionParameter, types[i], args[i]);
}
EmitLabel(s, s.builder.AllocateId());
const auto descriptor = args[0], node = args[1], extent = args[2];
Vec3 origin, direction, inverse;
for (uint32_t i = 0; i < 3; ++i) {
origin[i] = Extract(s, f, args[3], i);
direction[i] = Extract(s, f, args[4], i);
inverse[i] = Extract(s, f, args[5], i);
}
std::array<uint32_t, 4> words;
for (uint32_t i = 0; i < 4; ++i) words[i] = Extract(s, u, descriptor, i);
const auto and_bits = [&](uint32_t value, uint32_t mask) {
return Binary(s, spv::OpBitwiseAnd, u, value, ConstantU32(s, mask));
};
const auto shr = [&](uint32_t value, uint32_t shift) {
return Binary(s, spv::OpShiftRightLogical, u, value, ConstantU32(s, shift));
};
const auto kind = and_bits(node, 7);
const auto triangle = Binary(s, spv::OpULessThan, b, kind, ConstantU32(s, 4));
const auto full = Binary(s, spv::OpIEqual, b, kind, ConstantU32(s, 5));
const auto half = Binary(s, spv::OpIEqual, b, kind, ConstantU32(s, 4));
const auto bary = Binary(s, spv::OpINotEqual, b, and_bits(words[3], 0x01000000), ConstantU32(s, 0));
const auto sort = Binary(s, spv::OpINotEqual, b, and_bits(words[1], 0x80000000), ConstantU32(s, 0));
const auto grow = and_bits(shr(words[1], 23), 0xff);
const auto base = Binary(s, spv::OpShiftLeftLogical, wide,
DeviceAddressFromWords(s, words[0], and_bits(words[1], 0xff)), ConstantDeviceAddress(s, 8));
const auto index = Unary(s, spv::OpUConvert, wide, shr(node, 3));
const auto last_index = Binary(s, spv::OpIAdd, wide, index,
Select(s, wide, full, ConstantDeviceAddress(s, 1), ConstantDeviceAddress(s, 0)));
const auto address = Binary(s, spv::OpIAdd, wide, base,
Binary(s, spv::OpShiftLeftLogical, wide, index, ConstantDeviceAddress(s, 6)));
const auto last_address = Binary(s, spv::OpIAdd, wide, base,
Binary(s, spv::OpShiftLeftLogical, wide, last_index, ConstantDeviceAddress(s, 6)));
auto valid = Binary(s, spv::OpIEqual, b, shr(words[3], 28), ConstantU32(s, 8));
const auto require = [&](uint32_t condition) {
valid = Binary(s, spv::OpLogicalAnd, b, valid, condition);
};
require(Binary(s, spv::OpULessThanEqual, b, kind, ConstantU32(s, 5)));
require(Binary(s, spv::OpULessThanEqual, b, last_index, DeviceAddressFromWords(s, words[2], and_bits(words[3], 0x3ff))));
require(Binary(s, spv::OpULessThan, b, last_address, ConstantDeviceAddress(s, uint64_t{1} << 48)));
require(Unary(s, spv::OpLogicalNot, b, Unary(s, spv::OpIsNan, b, extent)));
for (uint32_t i = 0; i < 3; ++i) {
for (const auto value: {origin[i], direction[i], inverse[i]})
require(Unary(s, spv::OpLogicalNot, b, Unary(s, spv::OpIsNan, b, value)));
require(Unary(s, spv::OpLogicalNot, b, Unary(s, spv::OpIsInf, b, origin[i])));
}
const std::array triangle_invalid {ConstantU32(s, 0x7f800000), ConstantU32(s, 0x3f800000),
ConstantU32(s, ~0u), ConstantU32(s, 0)};
const std::array box_invalid {ConstantU32(s, ~0u), ConstantU32(s, ~0u),
ConstantU32(s, ~0u), ConstantU32(s, ~0u)};
std::array<uint32_t, 4> invalid_words;
for (uint32_t i = 0; i < 4; ++i)
invalid_words[i] = Select(s, u, triangle, triangle_invalid[i], box_invalid[i]);
const auto invalid = Vector(s, vec4, invalid_words);
const auto result = EmitValueOrDefaultIfCondition(s, valid, vec4, invalid, [&] {
const auto first = GetBdaPointer(s, address);
const auto second = EmitValueOrDefaultIfCondition(s, full, wide, ConstantDeviceAddress(s, 0), [&] {
return GetBdaPointer(s, Binary(s, spv::OpIAdd, wide, address, ConstantDeviceAddress(s, 64)));
});
const auto present = Binary(s, spv::OpLogicalAnd, b,
Binary(s, spv::OpINotEqual, b, first, ConstantDeviceAddress(s, 0)),
Binary(s, spv::OpLogicalOr, b, Unary(s, spv::OpLogicalNot, b, full),
Binary(s, spv::OpINotEqual, b, second, ConstantDeviceAddress(s, 0))));
return EmitValueOrDefaultIfCondition(s, present, vec4, invalid, [&] {
const auto tri_label = s.builder.AllocateId(), box_label = s.builder.AllocateId();
const auto merge = s.builder.AllocateId();
s.builder.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
s.builder.AddFunction(spv::OpBranchConditional, triangle, tri_label, box_label);
EmitLabel(s, tri_label);
const auto tri_value = Triangle(s, first, kind, bary, origin, direction);
const auto tri_exit = s.current_label;
s.builder.AddFunction(spv::OpBranch, merge);
EmitLabel(s, box_label);
const auto box_value = Boxes(s, first, second, half, grow, sort, extent, origin, inverse);
const auto box_exit = s.current_label;
s.builder.AddFunction(spv::OpBranch, merge);
EmitLabel(s, merge);
const auto value = s.builder.AllocateId();
s.builder.AddFunction(spv::OpPhi, vec4, value, tri_value, tri_exit, box_value, box_exit);
return value;
});
});
s.builder.AddFunction(spv::OpReturnValue, result);
s.builder.AddFunction(spv::OpFunctionEnd);
}
uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& s = ctx.state;
const auto* ray = ctx.ImageAddress(inst.Arg(1));
if (ray == nullptr || ray->NumArgs() < 11 || s.bvh_intersect_function == 0)
ctx.Fail(inst, "BVH intersection requires eleven address components and its shared function");
const auto vector = [&](uint32_t first) {
Vec3 values;
for (uint32_t i = 0; i < 3; ++i)
values[i] = Unary(s, spv::OpBitcast, TypeF32(s), ctx.Arg(*ray, first + i));
return Vector(s, TypeF32Vector(s, 3), values);
};
const auto descriptor = ctx.Arg(inst, 0), node = ctx.Arg(*ray, 0);
const auto extent = Unary(s, spv::OpBitcast, TypeF32(s), ctx.Arg(*ray, 1));
const auto origin = vector(2), direction = vector(5), inverse = vector(8);
return EmitValueOrDefaultIfCondition(s, ctx.Arg(inst, 2), TypeU32Vector(s, 4),
ConstantU32CompositeZero(s, 4), [&] {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpFunctionCall, TypeU32Vector(s, 4), result,
s.bvh_intersect_function, descriptor, node, extent, origin, direction, inverse);
return result;
});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -294,6 +294,7 @@ inline constexpr auto EmitDataAppend = EmitAppendConsume;
inline constexpr auto EmitDataConsume = EmitAppendConsume;
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitImageQueryDimensions = EmitImage;
inline constexpr auto EmitImageQueryLod = EmitImage;
inline constexpr auto EmitImageRead = EmitImage;
@@ -58,6 +58,7 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
struct SpirvRequirements {
bool bvh = false;
bool subgroup_ballot = false;
bool subgroup_shuffle = false;
bool subgroup_local_invocation_id = false;
@@ -96,6 +97,7 @@ struct EmitterState {
uint32_t bda_pagetable_variable = 0;
uint32_t fault_buffer_variable = 0;
uint32_t bda_pointer_function = 0;
uint32_t bvh_intersect_function = 0;
uint32_t gds_variable = 0;
uint32_t gds_length = 0;
uint32_t push_constant_variable = 0;
@@ -487,6 +489,10 @@ uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state);
void DefineBvhIntersect(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value);
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high);
// These templates accept local lambdas from several emitter translation units.
template <typename Fn>
@@ -1,6 +1,7 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include <algorithm>
@@ -105,31 +106,23 @@ uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t ad
return result;
}
uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t low,
uint32_t high) {
auto& state = ctx.state;
auto immediate = static_cast<int32_t>(mem.offset);
uint32_t AddAddressOffset(EmitterState& state, const IR::MemoryInfo& mem, uint32_t low,
uint32_t& high) {
const auto immediate = static_cast<int32_t>(mem.offset);
if (immediate == 0) return low;
const auto immediate_low = ConstantU32(state, static_cast<uint32_t>(immediate));
const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u);
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
return AddU64Low(state, low, high, immediate_low, immediate_high, high);
}
uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t low,
uint32_t high) {
auto& state = ctx.state;
low = AddAddressOffset(state, mem, low, high);
const auto valid = Binary(state, spv::OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
}
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
return state.builder.Constant(spv::OpConstant, TypeScalarU64(state),
static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u));
}
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
const auto low64 = Unary(state, spv::OpUConvert, TypeScalarU64(state), low);
const auto high64 = Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state),
Unary(state, spv::OpUConvert, TypeScalarU64(state), high),
ConstantDeviceAddress(state, 32));
return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low64, high64);
}
uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
auto& state = ctx.state;
auto low = ctx.Arg(inst, 1);
@@ -186,17 +179,9 @@ void RecordBdaFault(EmitterState& state, uint32_t page) {
Binary(state, spv::OpBitwiseOr, TypeU32(state), value, bit));
}
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), result,
state.bda_pointer_function, address);
return result;
}
uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
auto& state = ctx.state;
const auto bda = GetBdaPointer(ctx, address);
const auto bda = GetBdaPointer(state, address);
const auto present =
Binary(state, spv::OpINotEqual, TypeBool(state), bda, ConstantDeviceAddress(state, 0));
return EmitValueOrZeroIfCondition(state, present, [&]() {
@@ -511,6 +496,50 @@ void StoreWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resour
resource.memory_access);
}
template <typename Fn>
void ForEachLocalFlatAccess(ValueEmitContext& ctx, const IR::Inst& inst, Fn&& emit) {
auto& state = ctx.state;
auto mem = ctx.Memory(inst);
auto high = ctx.Arg(inst, 2);
const auto low = AddAddressOffset(state, mem, ctx.Arg(inst, 1), high);
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), low, ConstantU32(state, 2));
const auto aligned = Binary(state, spv::OpIEqual, TypeBool(state), ConstantU32(state, 0),
Binary(state, spv::OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, 3)));
for (const auto kind: {IR::ResourceKind::Scratch, IR::ResourceKind::Lds}) {
mem.kind = kind;
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto aperture = kind == IR::ResourceKind::Scratch ? Decoder::PrivateApertureHigh
: Decoder::SharedApertureHigh;
const auto selected = Binary(state, spv::OpIEqual, TypeBool(state), high, ConstantU32(state, aperture));
const auto valid = AndCondition(state, aligned,
AndCondition(state, selected, EmitMemoryElementInBounds(state, resource, index)));
emit(resource, index, valid);
}
}
uint32_t LoadLocalFlat(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
uint32_t result = 0;
ForEachLocalFlatAccess(ctx, inst, [&](const auto& resource, uint32_t index, uint32_t valid) {
const auto value = EmitValueOrZeroIfCondition(ctx.state, valid, [&]() {
return LoadWordInBounds(ctx, resource, index);
});
result = result == 0 ? value : Binary(ctx.state, spv::OpBitwiseOr, TypeU32(ctx.state), result, value);
});
return result;
});
}
void StoreLocalFlat(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
ForEachLocalFlatAccess(ctx, inst, [&](const auto& resource, uint32_t index, uint32_t valid) {
EmitIfCondition(ctx.state, valid, [&]() {
StoreWordInBounds(ctx, resource, index, ctx.Arg(inst, inst.NumArgs() - 2));
});
});
});
}
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t data) {
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
@@ -961,6 +990,27 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
} // namespace
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
return state.builder.Constant(spv::OpConstant, TypeScalarU64(state),
static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u));
}
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
const auto low64 = Unary(state, spv::OpUConvert, TypeScalarU64(state), low);
const auto high64 = Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state),
Unary(state, spv::OpUConvert, TypeScalarU64(state), high),
ConstantDeviceAddress(state, 32));
return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low64, high64);
}
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeScalarU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) {
return;
@@ -1243,7 +1293,9 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto shared_components = IR::SharedComponentCount(op);
const auto address_info = IR::AddressOpcodeInfoOf(op);
uint32_t value;
if (buffer_components > 1u)
if (mem.kind == IR::ResourceKind::FlatLocal)
value = LoadLocalFlat(ctx, inst);
else if (buffer_components > 1u)
value = LoadWideBuffer(ctx, inst, buffer_components);
else if (shared_components > 1u)
value = LoadWideShared(ctx, inst, shared_components);
@@ -1270,7 +1322,9 @@ void EmitStoreMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto buffer_components = IR::BufferComponentCount(op);
const auto shared_components = IR::SharedComponentCount(op);
const auto type = inst.Arg(inst.NumArgs() - 2).GetType();
if (buffer_components > 1u)
if (mem.kind == IR::ResourceKind::FlatLocal)
StoreLocalFlat(ctx, inst);
else if (buffer_components > 1u)
StoreWideBuffer(ctx, inst, buffer_components);
else if (shared_components > 1u)
StoreWideShared(ctx, inst, shared_components);
@@ -679,6 +679,7 @@ void EmitProgram(EmitterState& state) {
}
}
DefineGetBdaPointer(state);
DefineBvhIntersect(state);
for (const auto* block: program.blocks) {
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
@@ -225,6 +225,7 @@ Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const Mim
case 0x09u: return Opcode::IMAGE_STORE_MIP;
case 0x0eu: return Opcode::IMAGE_GET_RESINFO;
case 0x60u: return Opcode::IMAGE_GET_LOD;
case 0xe6u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
default: return Opcode::UNSUPPORTED;
}
}
@@ -253,6 +254,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
}
switch (opcode) {
case 0xe6u: return 11u;
case 0x0eu: return 1u;
case 0x01u:
case 0x09u: return ImageCoordComponents(dimension) + 1u;
@@ -360,6 +362,11 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
if (d16 && !supports_d16) {
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data");
}
if (opcode == 0xe6u &&
(a16 || !r128 || inst.dmask != 0xfu || (nsa_dwords != 0u && nsa_dwords != 3u))) {
SetUnsupported(inst, Family::MIMG, opcode,
"BVH intersection requires eleven full-float ray DWORDs and R128/dmask:0xf");
}
DecodeVectorGpr(vdata, inst.dst);
DecodeVectorGpr(vaddr, inst.src0);
@@ -254,6 +254,8 @@ void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
case 125u: operand.kind = OperandKind::Null; return;
case 126u: operand.kind = OperandKind::ExecLo; return;
case 127u: operand.kind = OperandKind::ExecHi; return;
case 235u: operand.kind = OperandKind::SharedBase; return;
case 237u: operand.kind = OperandKind::PrivateBase; return;
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
case 248u:
operand.kind = OperandKind::FloatInlineConstant;
@@ -404,7 +406,6 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
program.instructions.clear();
program.instructions.reserve(code.size());
program.code = code;
program.has_bvh = false;
std::vector<bool> branch_targets;
for (uint32_t word_index = 0; word_index < code.size();) {
@@ -413,10 +414,6 @@ void DecodeProgram(std::span<const uint32_t> code, Program& program) {
const auto& inst = program.instructions.back();
word_index += inst.word_count;
if (inst.family == Family::MIMG && (inst.opcode_id == 0xe6u || inst.opcode_id == 0xe7u)) {
program.has_bvh = true;
return;
}
if (IsDirectBranch(inst.opcode)) {
const auto target_index = inst.branch_target / sizeof(uint32_t);
@@ -455,6 +452,8 @@ std::string OperandToString(const Operand& operand) {
case OperandKind::Scc: text = "scc"; break;
case OperandKind::M0: text = "m0"; break;
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
case OperandKind::SharedBase: text = "shared_base"; break;
case OperandKind::PrivateBase: text = "private_base"; break;
case OperandKind::Null: text = "null"; break;
default: text = "unknown"; break;
}
@@ -10,6 +10,10 @@
namespace Libs::Graphics::ShaderRecompiler::Decoder {
// PS5 flat stack addresses use these SH_MEM_BASES aperture tags in VA[63:32].
constexpr uint32_t PrivateApertureHigh = 0x70000000u;
constexpr uint32_t SharedApertureHigh = 0x80000000u;
enum class Family {
Unknown,
SOP1,
@@ -569,6 +573,7 @@ enum class Opcode {
DS_WRITE_B128,
DS_WRITE_ADDTID_B32,
DS_READ_ADDTID_B32,
IMAGE_BVH_INTERSECT_RAY,
IMAGE_GET_RESINFO,
IMAGE_GET_LOD,
IMAGE_LOAD,
@@ -636,6 +641,8 @@ enum class OperandKind {
Scc,
M0,
PopsExitingWaveId,
SharedBase,
PrivateBase,
Null,
Vgpr,
};
@@ -747,7 +754,6 @@ struct Instruction {
struct Program {
std::span<const uint32_t> code;
std::vector<Instruction> instructions;
bool has_bvh = false;
};
// Code spans are trusted to contain complete instructions, valid branch targets, and 32-bit PCs.
@@ -883,6 +883,15 @@ void Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
void Translator::EmitMemory(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()});
for (uint32_t component = 0; component < 4; ++component) {
WriteOperand(OffsetOperand(inst.dst, component),
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(component)}));
}
return;
}
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
case Decoder::Opcode::S_LOAD_DWORDX4:
@@ -135,6 +135,8 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
case Decoder::OperandKind::IntegerInlineConstant:
case Decoder::OperandKind::FloatInlineConstant: return IR::U32(IR::Value(operand.value));
case Decoder::OperandKind::Null:
case Decoder::OperandKind::SharedBase:
case Decoder::OperandKind::PrivateBase:
case Decoder::OperandKind::PopsExitingWaveId: return IR::U32(IR::Value(0u));
case Decoder::OperandKind::Sgpr:
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
@@ -474,6 +476,12 @@ IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
}
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
if (operand.kind == Decoder::OperandKind::PrivateBase ||
operand.kind == Decoder::OperandKind::SharedBase) {
return {IR::U32(IR::Value(0u)), IR::U32(IR::Value(
operand.kind == Decoder::OperandKind::PrivateBase ? Decoder::PrivateApertureHigh
: Decoder::SharedApertureHigh))};
}
if (operand.kind == Decoder::OperandKind::ExecLo) {
return {ir.GetExecLo(), ir.GetExecHi()};
}
+3 -1
View File
@@ -30,6 +30,7 @@ enum class ResourceKind {
Buffer,
IndirectBuffer,
Flat,
FlatLocal,
Global,
Scratch,
Lds,
@@ -40,7 +41,8 @@ enum class ResourceKind {
[[nodiscard]] constexpr bool IsAddressResourceKind(ResourceKind kind) {
return kind == ResourceKind::ScalarAddress || kind == ResourceKind::Flat ||
kind == ResourceKind::Global || kind == ResourceKind::Scratch;
kind == ResourceKind::FlatLocal || kind == ResourceKind::Global ||
kind == ResourceKind::Scratch;
}
struct MemoryInfo {
@@ -284,6 +284,7 @@ VALUE_OPCODE(DataConsume, U32, U32, U1)
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1)
VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
@@ -44,6 +44,34 @@ uint32_t PossibleU32Bits(Value value) {
}
}
bool IsNoncanonicalFlatAddress(Value high, Value active) {
high = high.Resolve();
active = active.Resolve();
// A predicated VGPR write supplies its new value on exactly the lanes which
// execute this access. Do not discard a selection under a different mask.
while (const auto* select = high.TryInstruction()) {
if (select->GetOpcode() != ValueOpcode::SelectU32 || select->Arg(0).Resolve() != active) break;
high = select->Arg(1).Resolve();
}
uint32_t lower = 0, upper = UINT32_MAX;
if (high.IsImmediate() && high.GetType() == Type::U32) {
lower = upper = high.U32();
} else if (const auto* inst = high.TryInstruction();
inst != nullptr && inst->GetOpcode() == ValueOpcode::UMedTri32) {
// The median lies between any two constant inputs, regardless of the third.
uint32_t count = 0;
for (size_t index = 0; index < inst->NumArgs(); ++index) {
const auto value = inst->Arg(index).Resolve();
if (!value.IsImmediate() || value.GetType() != Type::U32) continue;
if (count++ == 0) lower = upper = value.U32();
else { lower = std::min(lower, value.U32()); upper = std::max(upper, value.U32()); }
}
if (count < 2) return false;
}
// Leave one high DWORD on each side for the signed instruction offset/carry.
return lower > 0x00008000u && upper < 0xffff7fffu;
}
Value CanonicalizeSampleAdjustDword3(Value value) {
for (;;) {
value = value.Resolve();
@@ -1649,6 +1677,10 @@ private:
void Collect(Inst& inst) {
const auto op = inst.GetOpcode();
if (op == ValueOpcode::BvhIntersect) {
m_info.uses_dma = true;
return;
}
const auto buffer = BufferAccessOf(op);
const auto address_info = AddressOpcodeInfoOf(op);
const auto image_info = ImageOpcodeInfoOf(op);
@@ -1695,6 +1727,15 @@ private:
if (!IsAddressResourceKind(memory.kind)) {
Fail(flags.pc, "address operation has invalid resource kind");
}
if (memory.kind == ResourceKind::Flat && memory.address_is_full &&
IsNoncanonicalFlatAddress(inst.Arg(2), inst.Arg(inst.NumArgs() - 1))) {
ValidateAddressHandle(inst.Arg(0), flags.pc);
if (memory.data_bits != 32u || m_program.scratch_dwords == 0) {
Fail(flags.pc, "local FLAT access requires DWORD data and per-thread scratch storage");
}
m_program.memory_info[flags.index].kind = ResourceKind::FlatLocal;
return;
}
if (memory.kind == ResourceKind::Scratch) {
handle = inst.Arg(0).Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetScratchResource ||