shader: compute DPP permute, rotate, mirror and share lanes with bit-field and XOR ops

This commit is contained in:
nmzik
2026-10-22 00:40:07 +02:00
parent 6123419437
commit 09a9654458
2 changed files with 57 additions and 59 deletions
@@ -4,25 +4,16 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
uint32_t lane_bits) {
const auto lane_mask = (1u << lane_bits) - 1u;
const auto group_base = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto selected0 = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), group_base, subid,
ConstantU32(state, ~lane_mask));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, lane_mask));
state.builder.AddFunction(spv::OpIMul, TypeU32(state), shift, lane,
ConstantU32(state, lane_bits));
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), selected0,
ConstantU32(state, control), shift);
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), selected, selected0,
ConstantU32(state, lane_mask));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, group_base, selected);
return {target, ConstantBool(state, true)};
// Each lane of a group takes its lane_bits-wide source index from control.
const auto lane = Binary(state, spv::OpBitwiseAnd, TypeU32(state), subid,
ConstantU32(state, (1u << lane_bits) - 1u));
const auto selected = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
state, ConstantU32(state, control),
Binary(state, spv::OpIMul, TypeU32(state), lane, ConstantU32(state, lane_bits)),
ConstantU32(state, lane_bits));
return {EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
state, subid, selected, ConstantU32(state, 0), ConstantU32(state, lane_bits)),
ConstantBool(state, true)};
}
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
@@ -53,42 +44,19 @@ DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uin
DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subid,
uint32_t amount) {
const auto row = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto minus = state.builder.AllocateId();
const auto plus = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
ConstantU32(state, 0xfffffff0u));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, 15));
state.builder.AddFunction(spv::OpUGreaterThanEqual, TypeBool(state), in_high, lane,
ConstantU32(state, amount));
state.builder.AddFunction(spv::OpISub, TypeU32(state), minus, lane, ConstantU32(state, amount));
state.builder.AddFunction(spv::OpIAdd, TypeU32(state), plus, lane,
ConstantU32(state, 16u - amount));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), selected, in_high, minus, plus);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row, selected);
return {target, ConstantBool(state, true)};
// Rotation wraps within the row, so only the low four lane bits change.
const auto rotated = Binary(state, spv::OpISub, TypeU32(state), subid, ConstantU32(state, amount));
return {EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(state, subid, rotated,
ConstantU32(state, 0),
ConstantU32(state, 4)),
ConstantBool(state, true)};
}
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row) {
const auto base_mask = half_row ? 0xfffffff8u : 0xfffffff0u;
const auto lane_mask = half_row ? 7u : 15u;
const auto base = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto mirrored = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), base, subid,
ConstantU32(state, base_mask));
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
ConstantU32(state, lane_mask));
state.builder.AddFunction(spv::OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask),
lane);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, base, mirrored);
return {target, ConstantBool(state, true)};
// Mirroring lane i of a row or half row reads lane (size - 1 - i), which flips its low bits.
return {Binary(state, spv::OpBitwiseXor, TypeU32(state), subid,
ConstantU32(state, half_row ? 7u : 15u)),
ConstantBool(state, true)};
}
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags) {
@@ -117,13 +85,10 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& fla
}
// row_share:N reads lane N of the caller's row of 16.
if (control >= 0x150u && control <= 0x15fu) {
const auto row = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
ConstantU32(state, 0xfffffff0u));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row,
ConstantU32(state, control & 0xfu));
return {target, ConstantBool(state, true)};
return {EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
state, subid, ConstantU32(state, control & 0xfu), ConstantU32(state, 0),
ConstantU32(state, 4)),
ConstantBool(state, true)};
}
if (control >= 0x160u && control <= 0x16fu) {
const auto target = state.builder.AllocateId();
+33
View File
@@ -27298,6 +27298,35 @@ TestCase VectorDppRowShare(u32 wave_size) {
return test;
}
// row_mirror, row_half_mirror and row_ror read lanes within the row; v1 = 100 + lane.
TestCase VectorDppMirrorAndRotate(u32 wave_size) {
using O = ShaderOpcode;
TestCase test;
test.name = wave_size == 32 ? "VectorDppMirrorAndRotateWave32" : "VectorDppMirrorAndRotateWave64";
auto &code = test.code;
AppendVMovU32(&code, 1, 100);
code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1));
for (const u32 control : {0x140u, 0x141u, 0x121u, 0x125u, 0x12fu}) {
code.push_back(EncodeVop1(0x01, 2, 250));
code.push_back(EncodeVop1Dpp(1, control));
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, u32(test.expected.size()));
for (u32 lane = 0; lane < wave_size; ++lane) {
const u32 source = control == 0x140u ? (lane | 15u) - (lane & 15u)
: control == 0x141u ? (lane | 7u) - (lane & 7u)
: (lane & ~15u) | ((lane - (control & 15u)) & 15u);
test.expected.push_back(100 + source);
}
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase VectorDppBankMaskPreservesDestination() {
using O = ShaderOpcode;
@@ -43058,6 +43087,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorDppRowXmask);
cases.push_back(VectorDppRowShare(32));
cases.push_back(VectorDppRowShare(64));
cases.push_back(VectorDppMirrorAndRotate(32));
cases.push_back(VectorDppMirrorAndRotate(64));
cases.push_back(ComputeWave64Minimum(false));
cases.push_back(ComputeWave64Minimum(true));
AddCase(VectorDppBankMaskPreservesDestination);
@@ -49148,6 +49179,8 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorDppRowXmask());
RunCase(&vulkan, VectorDppRowShare(32));
RunCase(&vulkan, VectorDppRowShare(64));
RunCase(&vulkan, VectorDppMirrorAndRotate(32));
RunCase(&vulkan, VectorDppMirrorAndRotate(64));
RunCase(&vulkan, VectorDppBankMaskPreservesDestination());
RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination());
return 0;