shader: generate SPIR-V dispatch and marshal handler arguments

This commit is contained in:
nmzik
2026-09-11 18:54:49 +04:00
parent 1d1f00bdef
commit 59ef88c959
8 changed files with 913 additions and 638 deletions
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include <array>
@@ -277,402 +277,364 @@ uint32_t EmitPackHalf(EmitterState& state, uint32_t src) {
} // namespace
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
uint32_t EmitConvertU16U32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitNative<spv::OpBitwiseAnd, IR::Type::U16>(ctx, arg0, ConstantU32(state, 0xffffu));
}
uint32_t EmitConvertU8U32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitNative<spv::OpBitwiseAnd, IR::Type::U8>(ctx, arg0, ConstantU32(state, 0xffu));
}
uint32_t EmitConvertF16F32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto op = inst.GetOpcode();
const auto unary = [&](uint32_t spirv, IR::Type type) {
ctx.Emit(inst, spirv, type, {ctx.Arg(inst, 0)});
return true;
};
const auto binary = [&](uint32_t spirv, IR::Type type) {
ctx.Emit(inst, spirv, type, {ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
};
const auto ext_unary = [&](uint32_t ext) {
ctx.Define(inst, EmitExt(state, TypeF32(state), ext, {ctx.Arg(inst, 0)}));
return true;
};
switch (op) {
case IR::ValueOpcode::BitCastU16F16:
case IR::ValueOpcode::BitCastF16U16:
case IR::ValueOpcode::ConvertU32U16:
case IR::ValueOpcode::ConvertU32U8:
case IR::ValueOpcode::BitCastU32F32: return unary(spv::OpBitcast, IR::Type::U32);
case IR::ValueOpcode::BitCastF32U32: return unary(spv::OpBitcast, IR::Type::F32);
case IR::ValueOpcode::ConvertU16U32:
ctx.Emit(inst, spv::OpBitwiseAnd, IR::Type::U16,
{ctx.Arg(inst, 0), ConstantU32(state, 0xffffu)});
return true;
case IR::ValueOpcode::ConvertU8U32:
ctx.Emit(inst, spv::OpBitwiseAnd, IR::Type::U8,
{ctx.Arg(inst, 0), ConstantU32(state, 0xffu)});
return true;
case IR::ValueOpcode::ConvertF16F32: {
const auto pair = state.builder.AllocateId();
state.builder.AddFunction({spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair,
ctx.Arg(inst, 0), ConstantF32(state, 0)});
ctx.Define(inst, EmitPackHalf(state, pair));
return true;
}
case IR::ValueOpcode::ConvertF32F16:
ctx.Define(inst, EmitF16BitsToF32(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::ConvertS32F32:
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), true));
return true;
case IR::ValueOpcode::ConvertU32F32:
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), false));
return true;
case IR::ValueOpcode::ConvertF32S32: {
const auto signed_value =
Unary(state, spv::OpBitcast, TypeI32(state), ctx.Arg(inst, 0));
ctx.Emit(inst, spv::OpConvertSToF, IR::Type::F32, {signed_value});
return true;
}
case IR::ValueOpcode::ConvertF32U32: return unary(spv::OpConvertUToF, IR::Type::F32);
case IR::ValueOpcode::CompositeConstructU64:
ctx.Emit(inst, spv::OpCompositeConstruct, IR::Type::U64,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::CompositeConstructU32x2:
ctx.Emit(inst, spv::OpCompositeConstruct, IR::Type::U32x2,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::CompositeConstructU32x3:
ctx.Emit(inst, spv::OpCompositeConstruct, IR::Type::U32x3,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::CompositeConstructF32x2:
ctx.Emit(inst, spv::OpCompositeConstruct, IR::Type::F32x2,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::CompositeConstructU32x4:
ctx.Emit(inst, spv::OpCompositeConstruct, IR::Type::U32x4,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)});
return true;
case IR::ValueOpcode::CompositeExtractU64:
case IR::ValueOpcode::CompositeExtractU32x2:
case IR::ValueOpcode::CompositeExtractU32x3:
case IR::ValueOpcode::CompositeExtractU32x4:
ctx.Emit(inst, spv::OpCompositeExtract, IR::Type::U32,
{ctx.Arg(inst, 0), inst.Arg(1).U32()});
return true;
case IR::ValueOpcode::PackHalf2x16:
ctx.Define(inst,
EmitExt(state, TypeU32(state), GLSLstd450PackHalf2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackSnorm2x16:
ctx.Define(inst,
EmitExt(state, TypeU32(state), GLSLstd450PackSnorm2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackUnorm2x16:
ctx.Define(inst,
EmitExt(state, TypeU32(state), GLSLstd450PackUnorm2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackFloat2x16Rtz: {
const auto low = EmitF32ToF16RtzBits(state, ctx.Arg(inst, 0));
const auto high =
Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
EmitF32ToF16RtzBits(state, ctx.Arg(inst, 1)), ConstantU32(state, 16));
ctx.Define(inst, Binary(state, spv::OpBitwiseOr, TypeU32(state), low, high));
return true;
}
case IR::ValueOpcode::FPAbs32:
ctx.Define(inst, EmitFAbsValue(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPNeg32:
ctx.Define(inst, EmitFNegateValue(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPSaturate32:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450FClamp,
{ctx.Arg(inst, 0), ConstantF32(state, 0),
ConstantF32(state, 0x3f800000u)}));
return true;
case IR::ValueOpcode::BitFieldInsert:
ctx.Emit(inst, spv::OpBitFieldInsert, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)});
return true;
case IR::ValueOpcode::BitFieldUExtract:
ctx.Emit(inst, spv::OpBitFieldUExtract, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::BitFieldSExtract:
ctx.Emit(inst, spv::OpBitFieldSExtract, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectU1:
ctx.Emit(inst, spv::OpSelect, IR::Type::U1,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectU32:
ctx.Emit(inst, spv::OpSelect, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectF32:
ctx.Emit(inst, spv::OpSelect, IR::Type::F32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::IAdd32: return binary(spv::OpIAdd, IR::Type::U32);
case IR::ValueOpcode::ISub32: return binary(spv::OpISub, IR::Type::U32);
case IR::ValueOpcode::IMul32: return binary(spv::OpIMul, IR::Type::U32);
case IR::ValueOpcode::UDiv32: return binary(spv::OpUDiv, IR::Type::U32);
case IR::ValueOpcode::IAdd64:
ctx.Define(inst, EmitAdd64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ISub64:
ctx.Define(inst, EmitSub64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::IMul64:
ctx.Define(inst, EmitMul64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::IAddCarry32:
ctx.Emit(inst, spv::OpIAddCarry, IR::Type::U32x2, {ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::SMulHi:
ctx.Define(inst, EmitMulHigh(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::UMulHi:
ctx.Define(inst, EmitMulHigh(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::IAbs32: {
const auto value = ctx.Arg(inst, 0);
const auto neg = Unary(state, spv::OpSNegate, TypeU32(state), value);
const auto negative =
Binary(state, spv::OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
ctx.Define(inst, Select(state, TypeU32(state), negative, neg, value));
return true;
}
case IR::ValueOpcode::ShiftLeftLogical32:
return binary(spv::OpShiftLeftLogical, IR::Type::U32);
case IR::ValueOpcode::ShiftRightLogical32:
return binary(spv::OpShiftRightLogical, IR::Type::U32);
case IR::ValueOpcode::ShiftRightArithmetic32:
return binary(spv::OpShiftRightArithmetic, IR::Type::U32);
case IR::ValueOpcode::ShiftLeftLogical64:
ctx.Define(inst,
inst.Arg(1).Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftLeftLogical, ctx.Arg(inst, 0),
inst.Arg(1).Resolve().U32())
: EmitShift64(state, spv::OpShiftLeftLogical, ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ShiftRightLogical64:
ctx.Define(inst,
inst.Arg(1).Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftRightLogical, ctx.Arg(inst, 0),
inst.Arg(1).Resolve().U32())
: EmitShift64(state, spv::OpShiftRightLogical, ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ShiftRightArithmetic64:
ctx.Define(inst,
inst.Arg(1).Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftRightArithmetic,
ctx.Arg(inst, 0), inst.Arg(1).Resolve().U32())
: EmitShift64(state, spv::OpShiftRightArithmetic, ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitwiseAnd32: return binary(spv::OpBitwiseAnd, IR::Type::U32);
case IR::ValueOpcode::BitwiseOr32: return binary(spv::OpBitwiseOr, IR::Type::U32);
case IR::ValueOpcode::BitwiseXor32: return binary(spv::OpBitwiseXor, IR::Type::U32);
case IR::ValueOpcode::BitwiseNot32: return unary(spv::OpNot, IR::Type::U32);
case IR::ValueOpcode::BitwiseAnd64:
ctx.Define(inst, Binary(state, spv::OpBitwiseAnd, TypeU64(state), ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitReverse32: return unary(spv::OpBitReverse, IR::Type::U32);
case IR::ValueOpcode::BitCount32: return unary(spv::OpBitCount, IR::Type::U32);
case IR::ValueOpcode::BitCount64: {
const auto pair =
ExtractPair(state, Unary(state, spv::OpBitCount, TypeU64(state), ctx.Arg(inst, 0)));
ctx.Define(inst, Binary(state, spv::OpIAdd, TypeU32(state), pair.low, pair.high));
return true;
}
case IR::ValueOpcode::FindILsb32: {
const auto value =
EmitExt(state, TypeI32(state), GLSLstd450FindILsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, Unary(state, spv::OpBitcast, TypeU32(state), value));
return true;
}
case IR::ValueOpcode::FindUMsb32: {
const auto value =
EmitExt(state, TypeI32(state), GLSLstd450FindUMsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, Unary(state, spv::OpBitcast, TypeU32(state), value));
return true;
}
case IR::ValueOpcode::FindUMsb64:
ctx.Define(inst, EmitFindMsb64(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::SMin32:
ctx.Define(inst, EmitMinMaxI32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::SMax32:
ctx.Define(inst, EmitMinMaxI32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::UMin32:
ctx.Define(inst, EmitMinMaxU32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::UMax32:
ctx.Define(inst, EmitMinMaxU32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::SMinTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true, false));
return true;
case IR::ValueOpcode::SMaxTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true, true));
return true;
case IR::ValueOpcode::UMinTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false, false));
return true;
case IR::ValueOpcode::UMaxTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false, true));
return true;
case IR::ValueOpcode::SMedTri32:
ctx.Define(inst,
EmitMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), true));
return true;
case IR::ValueOpcode::UMedTri32:
ctx.Define(
inst, EmitMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), false));
return true;
case IR::ValueOpcode::SLessThan32: return binary(spv::OpSLessThan, IR::Type::U1);
case IR::ValueOpcode::ULessThan32: return binary(spv::OpULessThan, IR::Type::U1);
case IR::ValueOpcode::IEqual32: return binary(spv::OpIEqual, IR::Type::U1);
case IR::ValueOpcode::SLessThanEqual32: return binary(spv::OpSLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::ULessThanEqual32: return binary(spv::OpULessThanEqual, IR::Type::U1);
case IR::ValueOpcode::SGreaterThan32: return binary(spv::OpSGreaterThan, IR::Type::U1);
case IR::ValueOpcode::UGreaterThan32: return binary(spv::OpUGreaterThan, IR::Type::U1);
case IR::ValueOpcode::INotEqual32: return binary(spv::OpINotEqual, IR::Type::U1);
case IR::ValueOpcode::SGreaterThanEqual32:
return binary(spv::OpSGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::UGreaterThanEqual32:
return binary(spv::OpUGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::IEqual64:
ctx.Define(inst, CompareEqual64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::INotEqual64:
ctx.Define(inst, CompareEqual64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::ULessThan64:
ctx.Define(inst, CompareOrdered64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
spv::OpULessThan, spv::OpULessThan));
return true;
case IR::ValueOpcode::SLessThan64:
ctx.Define(inst, CompareOrdered64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
spv::OpSLessThan, spv::OpULessThan));
return true;
case IR::ValueOpcode::UGreaterThan64:
ctx.Define(inst, CompareOrdered64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
spv::OpUGreaterThan, spv::OpUGreaterThan));
return true;
case IR::ValueOpcode::LogicalOr: return binary(spv::OpLogicalOr, IR::Type::U1);
case IR::ValueOpcode::LogicalAnd: return binary(spv::OpLogicalAnd, IR::Type::U1);
case IR::ValueOpcode::LogicalXor: return binary(spv::OpLogicalNotEqual, IR::Type::U1);
case IR::ValueOpcode::LogicalNot: return unary(spv::OpLogicalNot, IR::Type::U1);
case IR::ValueOpcode::FPOrdEqual32: return binary(spv::OpFOrdEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordEqual32: return binary(spv::OpFUnordEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdNotEqual32: return binary(spv::OpFOrdNotEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordNotEqual32: return binary(spv::OpFUnordNotEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdLessThan32: return binary(spv::OpFOrdLessThan, IR::Type::U1);
case IR::ValueOpcode::FPUnordLessThan32: return binary(spv::OpFUnordLessThan, IR::Type::U1);
case IR::ValueOpcode::FPOrdGreaterThan32:
return binary(spv::OpFOrdGreaterThan, IR::Type::U1);
case IR::ValueOpcode::FPUnordGreaterThan32:
return binary(spv::OpFUnordGreaterThan, IR::Type::U1);
case IR::ValueOpcode::FPOrdLessThanEqual32:
return binary(spv::OpFOrdLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordLessThanEqual32:
return binary(spv::OpFUnordLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdGreaterThanEqual32:
return binary(spv::OpFOrdGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordGreaterThanEqual32:
return binary(spv::OpFUnordGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPIsNan32:
ctx.Emit(inst, spv::OpFUnordNotEqual, IR::Type::U1,
{ctx.Arg(inst, 0), ctx.Arg(inst, 0)});
return true;
case IR::ValueOpcode::FPCmpClass32:
ctx.Define(inst, EmitClassMaskF32(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::FPAdd32: return binary(spv::OpFAdd, IR::Type::F32);
case IR::ValueOpcode::FPSub32: return binary(spv::OpFSub, IR::Type::F32);
case IR::ValueOpcode::FPMul32: return binary(spv::OpFMul, IR::Type::F32);
case IR::ValueOpcode::FPFma32:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450Fma,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)}));
return true;
case IR::ValueOpcode::FPMin32:
ctx.Define(inst, EmitMinMaxF32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::FPMax32:
ctx.Define(inst, EmitMinMaxF32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::FPMinTri32:
ctx.Define(inst, EmitFMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false));
return true;
case IR::ValueOpcode::FPMaxTri32:
ctx.Define(inst, EmitFMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true));
return true;
case IR::ValueOpcode::FPMedTri32:
ctx.Define(inst,
EmitFMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)));
return true;
case IR::ValueOpcode::FPRecip32: {
const auto source = EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0));
ctx.Define(inst, Binary(state, spv::OpFDiv, TypeF32(state),
ConstantF32(state, 0x3f800000u), source));
return true;
}
case IR::ValueOpcode::FPRecipIFlag32:
// Integer-to-float inputs used by IFLAG cannot be denormal.
ctx.Define(inst, Binary(state, spv::OpFDiv, TypeF32(state),
ConstantF32(state, 0x3f800000u), ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPRecipSqrt32:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450InverseSqrt,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPSqrt:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450Sqrt,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPSin:
case IR::ValueOpcode::FPCos: {
auto source = EmitTrigCycleF32(state, ctx.Arg(inst, 0), op == IR::ValueOpcode::FPSin);
source =
Binary(state, spv::OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
ctx.Define(inst, EmitExt(state, TypeF32(state),
op == IR::ValueOpcode::FPSin ? GLSLstd450Sin : GLSLstd450Cos,
{source}));
return true;
}
case IR::ValueOpcode::FPExp2:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450Exp2,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPLog2:
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450Log2,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPLdexp: {
const auto exponent = Unary(state, spv::OpBitcast, TypeI32(state), ctx.Arg(inst, 1));
ctx.Define(inst, EmitExt(state, TypeF32(state), GLSLstd450Ldexp,
{ctx.Arg(inst, 0), exponent}));
return true;
}
case IR::ValueOpcode::FPRoundEven32: return ext_unary(GLSLstd450RoundEven);
case IR::ValueOpcode::FPFloor32: return ext_unary(GLSLstd450Floor);
case IR::ValueOpcode::FPCeil32: return ext_unary(GLSLstd450Ceil);
case IR::ValueOpcode::FPTrunc32: return ext_unary(GLSLstd450Trunc);
case IR::ValueOpcode::FPFract32: return ext_unary(GLSLstd450Fract);
default: return false;
}
const auto pair = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, ConstantF32(state, 0)});
return EmitPackHalf(state, pair);
}
uint32_t EmitConvertF32F16(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitF16BitsToF32(state, arg0);
}
uint32_t EmitConvertS32F32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitF32ToU32(state, arg0, true);
}
uint32_t EmitConvertU32F32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitF32ToU32(state, arg0, false);
}
uint32_t EmitConvertF32S32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
return EmitNative<spv::OpConvertSToF, IR::Type::F32>(ctx, signed_value);
}
uint32_t EmitCompositeExtractU64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
return EmitNative<spv::OpCompositeExtract, IR::Type::U32>(ctx, arg0, arg1.U32());
}
uint32_t EmitPackHalf2x16(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeU32(state), GLSLstd450PackHalf2x16, {arg0});
}
uint32_t EmitPackSnorm2x16(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeU32(state), GLSLstd450PackSnorm2x16, {arg0});
}
uint32_t EmitPackUnorm2x16(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeU32(state), GLSLstd450PackUnorm2x16, {arg0});
}
uint32_t EmitPackFloat2x16Rtz(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
const auto low = EmitF32ToF16RtzBits(state, arg0);
const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
EmitF32ToF16RtzBits(state, arg1), ConstantU32(state, 16));
return Binary(state, spv::OpBitwiseOr, TypeU32(state), low, high);
}
uint32_t EmitFPAbs32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitFAbsValue(state, arg0);
}
uint32_t EmitFPNeg32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitFNegateValue(state, arg0);
}
uint32_t EmitFPSaturate32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450FClamp,
{arg0, ConstantF32(state, 0), ConstantF32(state, 0x3f800000u)});
}
uint32_t EmitIAdd64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitAdd64(state, arg0, arg1);
}
uint32_t EmitISub64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitSub64(state, arg0, arg1);
}
uint32_t EmitIMul64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMul64(state, arg0, arg1);
}
uint32_t EmitSMulHi(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMulHigh(state, arg0, arg1, true);
}
uint32_t EmitUMulHi(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMulHigh(state, arg0, arg1, false);
}
uint32_t EmitIAbs32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto value = arg0;
const auto neg = Unary(state, spv::OpSNegate, TypeU32(state), value);
const auto negative =
Binary(state, spv::OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
return Select(state, TypeU32(state), negative, neg, value);
}
uint32_t EmitShiftLeftLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
auto& state = ctx.state;
return arg1.Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftLeftLogical, arg0, arg1.Resolve().U32())
: EmitShift64(state, spv::OpShiftLeftLogical, arg0, ctx.Def(arg1));
}
uint32_t EmitShiftRightLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
auto& state = ctx.state;
return arg1.Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftRightLogical, arg0, arg1.Resolve().U32())
: EmitShift64(state, spv::OpShiftRightLogical, arg0, ctx.Def(arg1));
}
uint32_t EmitShiftRightArithmetic64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1) {
auto& state = ctx.state;
return arg1.Resolve().IsImmediate()
? EmitConstantShift64(state, spv::OpShiftRightArithmetic, arg0, arg1.Resolve().U32())
: EmitShift64(state, spv::OpShiftRightArithmetic, arg0, ctx.Def(arg1));
}
uint32_t EmitBitwiseAnd64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return Binary(state, spv::OpBitwiseAnd, TypeU64(state), arg0, arg1);
}
uint32_t EmitBitCount64(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto pair = ExtractPair(state, Unary(state, spv::OpBitCount, TypeU64(state), arg0));
return Binary(state, spv::OpIAdd, TypeU32(state), pair.low, pair.high);
}
uint32_t EmitFindILsb32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindILsb, {arg0});
return Unary(state, spv::OpBitcast, TypeU32(state), value);
}
uint32_t EmitFindUMsb32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto value = EmitExt(state, TypeI32(state), GLSLstd450FindUMsb, {arg0});
return Unary(state, spv::OpBitcast, TypeU32(state), value);
}
uint32_t EmitFindUMsb64(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitFindMsb64(state, arg0);
}
uint32_t EmitSMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxI32Value(state, arg0, arg1, false);
}
uint32_t EmitSMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxI32Value(state, arg0, arg1, true);
}
uint32_t EmitUMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxU32Value(state, arg0, arg1, false);
}
uint32_t EmitUMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxU32Value(state, arg0, arg1, true);
}
uint32_t EmitSMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMinMax3(state, arg0, arg1, arg2, true, false);
}
uint32_t EmitSMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMinMax3(state, arg0, arg1, arg2, true, true);
}
uint32_t EmitUMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMinMax3(state, arg0, arg1, arg2, false, false);
}
uint32_t EmitUMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMinMax3(state, arg0, arg1, arg2, false, true);
}
uint32_t EmitSMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMed3(state, arg0, arg1, arg2, true);
}
uint32_t EmitUMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitMed3(state, arg0, arg1, arg2, false);
}
uint32_t EmitIEqual64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return CompareEqual64(state, arg0, arg1, false);
}
uint32_t EmitINotEqual64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return CompareEqual64(state, arg0, arg1, true);
}
uint32_t EmitULessThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return CompareOrdered64(state, arg0, arg1, spv::OpULessThan, spv::OpULessThan);
}
uint32_t EmitSLessThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return CompareOrdered64(state, arg0, arg1, spv::OpSLessThan, spv::OpULessThan);
}
uint32_t EmitUGreaterThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return CompareOrdered64(state, arg0, arg1, spv::OpUGreaterThan, spv::OpUGreaterThan);
}
uint32_t EmitFPIsNan32(ValueEmitContext& ctx, uint32_t arg0) {
return EmitNative<spv::OpFUnordNotEqual, IR::Type::U1>(ctx, arg0, arg0);
}
uint32_t EmitFPCmpClass32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitClassMaskF32(state, arg0, arg1);
}
uint32_t EmitFPFma32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Fma, {arg0, arg1, arg2});
}
uint32_t EmitFPMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxF32Value(state, arg0, arg1, false);
}
uint32_t EmitFPMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
return EmitMinMaxF32Value(state, arg0, arg1, true);
}
uint32_t EmitFPMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitFMinMax3(state, arg0, arg1, arg2, false);
}
uint32_t EmitFPMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitFMinMax3(state, arg0, arg1, arg2, true);
}
uint32_t EmitFPMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2) {
auto& state = ctx.state;
return EmitFMed3(state, arg0, arg1, arg2);
}
uint32_t EmitFPRecip32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
const auto source = EmitFlushF32DenormToSignedZero(state, arg0);
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), source);
}
uint32_t EmitFPRecipIFlag32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
// Integer-to-float inputs used by IFLAG cannot be denormal.
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), arg0);
}
uint32_t EmitFPRecipSqrt32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450InverseSqrt,
{EmitFlushF32DenormToSignedZero(state, arg0)});
}
uint32_t EmitFPSqrt(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Sqrt,
{EmitFlushF32DenormToSignedZero(state, arg0)});
}
uint32_t EmitFPExp2(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Exp2,
{EmitFlushF32DenormToSignedZero(state, arg0)});
}
uint32_t EmitFPLog2(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Log2,
{EmitFlushF32DenormToSignedZero(state, arg0)});
}
uint32_t EmitFPLdexp(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1) {
auto& state = ctx.state;
const auto exponent = Unary(state, spv::OpBitcast, TypeI32(state), arg1);
return EmitExt(state, TypeF32(state), GLSLstd450Ldexp, {arg0, exponent});
}
uint32_t EmitFPRoundEven32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450RoundEven, {arg0});
}
uint32_t EmitFPFloor32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Floor, {arg0});
}
uint32_t EmitFPCeil32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Ceil, {arg0});
}
uint32_t EmitFPTrunc32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Trunc, {arg0});
}
uint32_t EmitFPFract32(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
return EmitExt(state, TypeF32(state), GLSLstd450Fract, {arg0});
}
uint32_t EmitFPSin(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
auto source = EmitTrigCycleF32(state, arg0, true);
source = Binary(state, spv::OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
return EmitExt(state, TypeF32(state), GLSLstd450Sin, {source});
}
uint32_t EmitFPCos(ValueEmitContext& ctx, uint32_t arg0) {
auto& state = ctx.state;
auto source = EmitTrigCycleF32(state, arg0, false);
source = Binary(state, spv::OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
return EmitExt(state, TypeF32(state), GLSLstd450Cos, {source});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include <algorithm>
@@ -426,7 +426,9 @@ uint32_t ConvertPositionToClipSpace(EmitterState& state, uint32_t position) {
return converted;
}
void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
} // namespace
void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& exp = ctx.Export(inst);
const auto exec = ctx.Arg(inst, 1);
@@ -515,179 +517,176 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
});
}
} // namespace
uint32_t EmitIdentity(ValueEmitContext&, uint32_t value) {
return value;
}
bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
void EmitVoid(ValueEmitContext&) {}
void EmitBarrier(ValueEmitContext& ctx) {
auto& state = ctx.state;
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction({spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, semantics)});
}
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
const auto index = inst.Arg(0).U32();
if (state.program.stage != ShaderType::Mesh || index >= IR::PushData::MeshDrawDwordCount) {
ctx.Fail(inst, "invalid mesh draw parameter");
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, index)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), result, pointer});
return result;
}
uint32_t EmitGetUserData(ValueEmitContext& ctx, IR::ScalarReg reg) {
auto& state = ctx.state;
switch (inst.GetOpcode()) {
case IR::ValueOpcode::Identity: ctx.Define(inst, ctx.Arg(inst, 0)); return true;
case IR::ValueOpcode::Void:
case IR::ValueOpcode::Reference:
case IR::ValueOpcode::ReferenceU32:
case IR::ValueOpcode::ControlNop:
case IR::ValueOpcode::Waitcnt:
case IR::ValueOpcode::Sendmsg:
case IR::ValueOpcode::TtraceData:
case IR::ValueOpcode::InstPrefetch: return true;
case IR::ValueOpcode::Barrier: {
const auto semantics =
spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsWorkgroupMemoryMask;
state.builder.AddFunction(
{spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, semantics)});
return true;
}
case IR::ValueOpcode::MeshAllocate: EmitMeshAllocate(ctx, inst); return true;
case IR::ValueOpcode::MeshDrawParameter: {
const auto index = inst.Arg(0).U32();
if (state.program.stage != ShaderType::Mesh ||
index >= IR::PushData::MeshDrawDwordCount) {
ctx.Fail(inst, "invalid mesh draw parameter");
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypePushConstantElementPointer(state),
pointer, state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, index)});
state.builder.AddFunction({spv::OpLoad, TypeU32(state), ctx.Result(inst), pointer});
return true;
}
case IR::ValueOpcode::GetUserData: {
const auto reg = inst.Arg(0).ScalarRegister();
uint32_t dword = 0;
if (!UserDataDwordIndex(state, reg, dword)) {
ctx.Define(inst, ConstantU32(state, 0));
} else {
ctx.Define(inst, EmitShaderDataDwordLoad(state, dword));
}
return true;
}
case IR::ValueOpcode::GetBuiltin:
ctx.Define(inst, EmitBuiltinU32(ctx, static_cast<IR::StageInputKind>(inst.Arg(0).U32()),
inst.Arg(1).U32()));
return true;
case IR::ValueOpcode::UndefU1:
case IR::ValueOpcode::UndefU8:
case IR::ValueOpcode::UndefU16:
case IR::ValueOpcode::UndefU32:
case IR::ValueOpcode::UndefU64:
state.builder.AddFunction({spv::OpUndef, ctx.TypeId(inst.GetType()), ctx.Result(inst)});
return true;
case IR::ValueOpcode::DppMoveU32: {
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto target = EmitDppTargetLane(state, flags.control);
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
if (flags.fetch_inactive) {
ctx.Define(inst, shuffled);
return true;
}
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
const auto can_fetch = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid, source_active});
ctx.Emit(inst, spv::OpSelect, IR::Type::U32,
{can_fetch, shuffled, ConstantU32(state, 0)});
return true;
}
case IR::ValueOpcode::DppUpdateU32: {
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto write = EmitDppWriteCondition(ctx, flags, ctx.Arg(inst, 2));
ctx.Emit(inst, spv::OpSelect, IR::Type::U32,
{write, ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
}
case IR::ValueOpcode::WqmU64:
ctx.Define(inst, EmitWqmU64(ctx.state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::LaneId:
ctx.Define(inst, EmitSubgroupLocalInvocationId(state));
return true;
case IR::ValueOpcode::Ballot: ctx.Define(inst, ctx.Ballot(inst.Arg(0))); return true;
case IR::ValueOpcode::ReadFirstLane: {
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto lane = ctx.FirstLane(ballot);
ctx.Define(inst, ctx.Shuffle(inst, 0, lane));
return true;
}
case IR::ValueOpcode::ReadLane:
ctx.Define(inst, ctx.Shuffle(inst, 0, ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::WriteLane: {
const auto hit = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), hit,
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2)});
ctx.Emit(inst, spv::OpSelect, IR::Type::U32, {hit, ctx.Arg(inst, 1), ctx.Arg(inst, 0)});
return true;
}
case IR::ValueOpcode::Permlane16U32: {
const auto flags = inst.Flags<IR::PermlaneFlags>();
const auto subid = EmitSubgroupLocalInvocationId(state);
const auto row = state.builder.AllocateId();
const auto row_value = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto lane8 = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto upper = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto shifted = state.builder.AllocateId();
const auto index = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
if (flags.x16) {
state.builder.AddFunction(
{spv::OpBitwiseXor, TypeU32(state), row_value, row, ConstantU32(state, 16)});
} else {
state.builder.AddFunction({spv::OpCopyObject, TypeU32(state), row_value, row});
}
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane8, lane, ConstantU32(state, 7)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane8, ConstantU32(state, 2)});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), upper, lane, ConstantU32(state, 8)});
state.builder.AddFunction({spv::OpSelect, TypeU32(state), selected, upper,
ctx.Arg(inst, 2), ctx.Arg(inst, 1)});
state.builder.AddFunction(
{spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), index, shifted, ConstantU32(state, 15)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row_value, index});
const auto shuffled = ctx.Shuffle(inst, 0, target);
uint32_t result = shuffled;
if (!flags.fetch_inactive) {
const auto source_exec = ctx.Shuffle(inst, 3, target);
result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpSelect, TypeU32(state), result, source_exec,
shuffled, ConstantU32(state, 0)});
}
ctx.Define(inst, result);
return true;
}
case IR::ValueOpcode::GetAttribute:
ctx.Define(inst, EmitAttribute(ctx, inst.Arg(0).U32(), inst.Arg(1).U32()));
return true;
case IR::ValueOpcode::GetInterpolationParameter:
ctx.Define(inst, EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(),
inst.Arg(2).U32()));
return true;
case IR::ValueOpcode::SetAttribute: EmitExport(ctx, inst); return true;
case IR::ValueOpcode::GetShaderBase:
// Guest S_GETPC values stay shader-relative in SPIR-V, matching the runtime ABI. The
// runtime descriptor evaluator supplies the mapped shader base for host-side planning.
ctx.Define(inst, ctx.Def(IR::Value(uint64_t {0})));
return true;
case IR::ValueOpcode::GetSrtResource:
case IR::ValueOpcode::GetBufferResource:
case IR::ValueOpcode::GetAddressResource:
case IR::ValueOpcode::GetScratchResource:
case IR::ValueOpcode::GetImageResource:
case IR::ValueOpcode::GetSamplerResource:
case IR::ValueOpcode::MakeImageAddress: return true;
default: return false;
uint32_t dword = 0;
if (!UserDataDwordIndex(state, reg, dword)) {
return ConstantU32(state, 0);
} else {
return EmitShaderDataDwordLoad(state, dword);
}
}
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index) {
return EmitBuiltinU32(ctx, static_cast<IR::StageInputKind>(kind.U32()), index.U32());
}
uint32_t EmitUndefU1(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
state.builder.AddFunction({spv::OpUndef, ctx.TypeId(inst.GetType()), result});
return result;
}
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto target = EmitDppTargetLane(state, flags.control);
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
if (flags.fetch_inactive) {
return shuffled;
}
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
const auto can_fetch = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpLogicalAnd, TypeBool(state), can_fetch, target.valid, source_active});
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx, can_fetch, shuffled,
ConstantU32(state, 0));
}
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto write = EmitDppWriteCondition(ctx, flags, ctx.Arg(inst, 2));
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx, write, ctx.Arg(inst, 0), ctx.Arg(inst, 1));
}
uint32_t EmitWqmU64(ValueEmitContext& ctx, uint32_t value) {
return EmitWqmU64(ctx.state, value);
}
uint32_t EmitLaneId(ValueEmitContext& ctx) {
auto& state = ctx.state;
return EmitSubgroupLocalInvocationId(state);
}
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
return ctx.Ballot(predicate);
}
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto ballot = ctx.Ballot(inst.Arg(1));
const auto lane = ctx.FirstLane(ballot);
return ctx.Shuffle(inst, 0, lane);
}
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
}
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto hit = state.builder.AllocateId();
state.builder.AddFunction({spv::OpIEqual, TypeBool(state), hit,
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2)});
return EmitNative<spv::OpSelect, IR::Type::U32>(ctx, hit, ctx.Arg(inst, 1), ctx.Arg(inst, 0));
}
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto flags = inst.Flags<IR::PermlaneFlags>();
const auto subid = EmitSubgroupLocalInvocationId(state);
const auto row = state.builder.AllocateId();
const auto row_value = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto lane8 = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto upper = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto shifted = state.builder.AllocateId();
const auto index = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), row, subid, ConstantU32(state, 0xfffffff0u)});
if (flags.x16) {
state.builder.AddFunction(
{spv::OpBitwiseXor, TypeU32(state), row_value, row, ConstantU32(state, 16)});
} else {
state.builder.AddFunction({spv::OpCopyObject, TypeU32(state), row_value, row});
}
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), lane8, lane, ConstantU32(state, 7)});
state.builder.AddFunction(
{spv::OpShiftLeftLogical, TypeU32(state), shift, lane8, ConstantU32(state, 2)});
state.builder.AddFunction(
{spv::OpUGreaterThanEqual, TypeBool(state), upper, lane, ConstantU32(state, 8)});
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), selected, upper, ctx.Arg(inst, 2), ctx.Arg(inst, 1)});
state.builder.AddFunction({spv::OpShiftRightLogical, TypeU32(state), shifted, selected, shift});
state.builder.AddFunction(
{spv::OpBitwiseAnd, TypeU32(state), index, shifted, ConstantU32(state, 15)});
state.builder.AddFunction({spv::OpBitwiseOr, TypeU32(state), target, row_value, index});
const auto shuffled = ctx.Shuffle(inst, 0, target);
uint32_t result = shuffled;
if (!flags.fetch_inactive) {
const auto source_exec = ctx.Shuffle(inst, 3, target);
result = state.builder.AllocateId();
state.builder.AddFunction(
{spv::OpSelect, TypeU32(state), result, source_exec, shuffled, ConstantU32(state, 0)});
}
return result;
}
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitAttribute(ctx, inst.Arg(0).U32(), inst.Arg(1).U32());
}
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
return EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(), inst.Arg(2).U32());
}
uint32_t EmitGetShaderBase(ValueEmitContext& ctx) {
// Guest S_GETPC values stay shader-relative in SPIR-V, matching the runtime ABI. The
// runtime descriptor evaluator supplies the mapped shader base for host-side planning.
return ctx.Def(IR::Value(uint64_t {0}));
}
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "must be lowered before SPIR-V emission");
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,5 +1,5 @@
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm>
@@ -556,23 +556,19 @@ uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
} // namespace
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
const auto image_info = IR::ImageOpcodeInfoOf(op);
if (image_info.access == IR::ImageAccess::None) {
return false;
}
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto image_arg = inst.Arg(0);
ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource);
const auto& image = state.program.info.images.at(mem.resource);
const auto* address = ctx.ImageAddress(inst.Arg(image_info.needs_sampler ? 2 : 1));
if (address == nullptr) return true;
if (op == IR::ValueOpcode::ImageQueryDimensions) {
state.builder.RequireCapability(spv::CapabilityImageQuery);
ctx.Define(inst, QueryDimensions(ctx, mem, *address));
return true;
return;
}
if (op == IR::ValueOpcode::ImageQueryLod) {
state.builder.RequireCapability(spv::CapabilityImageQuery);
@@ -594,7 +590,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
state.builder.AddFunction({spv::OpCompositeConstruct, TypeU32Vector(state, 4), result,
values[0], values[1], values[2], values[3]});
ctx.Define(inst, result);
return true;
return;
}
if (op == IR::ValueOpcode::ImageRead) {
const auto dimension = image.dimension;
@@ -623,7 +619,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
false, mem);
}));
return true;
return;
}
if (op == IR::ValueOpcode::ImageWrite) {
const bool uint_image = image.numeric_class == Prospero::TextureNumericClass::Uint;
@@ -637,7 +633,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
EmitStorageImageWrite(state, mem.resource, mip_lod, coord, texel);
});
return true;
return;
}
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
const auto dimension = image.dimension;
@@ -648,7 +644,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (dref && state.program.info.images[mem.resource].conversion_format !=
Prospero::BufferFormat::kInvalid) {
ctx.Fail(inst, "uses depth comparison with a packed integer image");
return true;
return;
}
const auto coord =
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
@@ -658,16 +654,16 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
ctx.Fail(inst, "has an unsupported 1D gather variant");
return true;
return;
}
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, coord, numeric_class);
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
numeric_class, false, mem, true));
return true;
return;
}
if (dimension == ImageDimension::Dim1DArray) {
ctx.Fail(inst, "has an unsupported 1D-array gather");
return true;
return;
}
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
const auto sample = state.builder.AllocateId();
@@ -709,7 +705,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
result_numeric_class, false, mem, true));
return true;
return;
}
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
HasFlag(mem, Decoder::ImageSampleFlagLod) ||
@@ -770,7 +766,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
result = UnpackImageTexel(ctx, mem, sample);
}
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
return true;
return;
}
const auto* handle = image_arg.ResolveInstruction();
const auto* source = image.source < state.program.descriptor_sources.size()
@@ -779,13 +775,13 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
source->indirect_image->key_arg >= handle->NumArgs()) {
ctx.Fail(inst, "has invalid indirect image key provenance");
return true;
return;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
return true;
return;
}
const auto LoadMapping = [&](uint32_t index) {
const auto pointer = state.builder.AllocateId();
@@ -867,7 +863,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
result = UnpackImageTexel(ctx, mem, result);
}
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
return true;
return;
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != 0u) {
@@ -888,9 +884,9 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitDeviceAtomicMemoryBarrier(state);
return old;
}));
return true;
return;
}
return false;
ctx.Fail(inst, "has no image SPIR-V emitter");
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,296 @@
#pragma once
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
// Direct SPIR-V operations share emission as well as argument/result marshalling.
template <spv::Op opcode, IR::Type type, typename... Args>
uint32_t EmitNative(ValueEmitContext& ctx, Args... args) {
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({opcode, ctx.TypeId(type), result, args...});
return result;
}
#define EMIT_NATIVE(name, opcode, type, ...) \
inline constexpr auto Emit##name = EmitNative<spv::opcode, IR::Type::type, __VA_ARGS__>;
EMIT_NATIVE(BitCastU16F16, OpBitcast, U32, uint32_t)
inline constexpr auto EmitBitCastF16U16 = EmitBitCastU16F16;
inline constexpr auto EmitConvertU32U16 = EmitBitCastU16F16;
inline constexpr auto EmitConvertU32U8 = EmitBitCastU16F16;
inline constexpr auto EmitBitCastU32F32 = EmitBitCastU16F16;
EMIT_NATIVE(BitCastF32U32, OpBitcast, F32, uint32_t)
uint32_t EmitConvertU16U32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertU8U32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertF16F32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertF32F16(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertS32F32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertU32F32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitConvertF32S32(ValueEmitContext& ctx, uint32_t arg0);
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
EMIT_NATIVE(CompositeConstructU64, OpCompositeConstruct, U64, uint32_t, uint32_t)
EMIT_NATIVE(CompositeConstructU32x2, OpCompositeConstruct, U32x2, uint32_t, uint32_t)
EMIT_NATIVE(CompositeConstructU32x3, OpCompositeConstruct, U32x3, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(CompositeConstructF32x2, OpCompositeConstruct, F32x2, uint32_t, uint32_t)
EMIT_NATIVE(CompositeConstructU32x4, OpCompositeConstruct, U32x4, uint32_t, uint32_t, uint32_t,
uint32_t)
uint32_t EmitCompositeExtractU64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
inline constexpr auto EmitCompositeExtractU32x2 = EmitCompositeExtractU64;
inline constexpr auto EmitCompositeExtractU32x3 = EmitCompositeExtractU64;
inline constexpr auto EmitCompositeExtractU32x4 = EmitCompositeExtractU64;
uint32_t EmitPackHalf2x16(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitPackSnorm2x16(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitPackUnorm2x16(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitPackFloat2x16Rtz(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPAbs32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPNeg32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPSaturate32(ValueEmitContext& ctx, uint32_t arg0);
EMIT_NATIVE(BitFieldInsert, OpBitFieldInsert, U32, uint32_t, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(BitFieldUExtract, OpBitFieldUExtract, U32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(BitFieldSExtract, OpBitFieldSExtract, U32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectU1, OpSelect, U1, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectU32, OpSelect, U32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
EMIT_NATIVE(ISub32, OpISub, U32, uint32_t, uint32_t)
EMIT_NATIVE(IMul32, OpIMul, U32, uint32_t, uint32_t)
EMIT_NATIVE(UDiv32, OpUDiv, U32, uint32_t, uint32_t)
uint32_t EmitIAdd64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitISub64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitIMul64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
EMIT_NATIVE(IAddCarry32, OpIAddCarry, U32x2, uint32_t, uint32_t)
uint32_t EmitSMulHi(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitUMulHi(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitIAbs32(ValueEmitContext& ctx, uint32_t arg0);
EMIT_NATIVE(ShiftLeftLogical32, OpShiftLeftLogical, U32, uint32_t, uint32_t)
EMIT_NATIVE(ShiftRightLogical32, OpShiftRightLogical, U32, uint32_t, uint32_t)
EMIT_NATIVE(ShiftRightArithmetic32, OpShiftRightArithmetic, U32, uint32_t, uint32_t)
uint32_t EmitShiftLeftLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
uint32_t EmitShiftRightLogical64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
uint32_t EmitShiftRightArithmetic64(ValueEmitContext& ctx, uint32_t arg0, IR::Value arg1);
EMIT_NATIVE(BitwiseAnd32, OpBitwiseAnd, U32, uint32_t, uint32_t)
EMIT_NATIVE(BitwiseOr32, OpBitwiseOr, U32, uint32_t, uint32_t)
EMIT_NATIVE(BitwiseXor32, OpBitwiseXor, U32, uint32_t, uint32_t)
EMIT_NATIVE(BitwiseNot32, OpNot, U32, uint32_t)
uint32_t EmitBitwiseAnd64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
EMIT_NATIVE(BitReverse32, OpBitReverse, U32, uint32_t)
EMIT_NATIVE(BitCount32, OpBitCount, U32, uint32_t)
uint32_t EmitBitCount64(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFindILsb32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFindUMsb32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFindUMsb64(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitSMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitSMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitUMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitUMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitSMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitSMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitUMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitUMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitSMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitUMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
EMIT_NATIVE(SLessThan32, OpSLessThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(ULessThan32, OpULessThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(IEqual32, OpIEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(SLessThanEqual32, OpSLessThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(ULessThanEqual32, OpULessThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(SGreaterThan32, OpSGreaterThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(UGreaterThan32, OpUGreaterThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(INotEqual32, OpINotEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(SGreaterThanEqual32, OpSGreaterThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(UGreaterThanEqual32, OpUGreaterThanEqual, U1, uint32_t, uint32_t)
uint32_t EmitIEqual64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitINotEqual64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitULessThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitSLessThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitUGreaterThan64(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
EMIT_NATIVE(LogicalOr, OpLogicalOr, U1, uint32_t, uint32_t)
EMIT_NATIVE(LogicalAnd, OpLogicalAnd, U1, uint32_t, uint32_t)
EMIT_NATIVE(LogicalXor, OpLogicalNotEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(LogicalNot, OpLogicalNot, U1, uint32_t)
EMIT_NATIVE(FPOrdEqual32, OpFOrdEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordEqual32, OpFUnordEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPOrdNotEqual32, OpFOrdNotEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordNotEqual32, OpFUnordNotEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPOrdLessThan32, OpFOrdLessThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordLessThan32, OpFUnordLessThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPOrdGreaterThan32, OpFOrdGreaterThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordGreaterThan32, OpFUnordGreaterThan, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPOrdLessThanEqual32, OpFOrdLessThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordLessThanEqual32, OpFUnordLessThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPOrdGreaterThanEqual32, OpFOrdGreaterThanEqual, U1, uint32_t, uint32_t)
EMIT_NATIVE(FPUnordGreaterThanEqual32, OpFUnordGreaterThanEqual, U1, uint32_t, uint32_t)
uint32_t EmitFPIsNan32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPCmpClass32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
EMIT_NATIVE(FPSub32, OpFSub, F32, uint32_t, uint32_t)
EMIT_NATIVE(FPMul32, OpFMul, F32, uint32_t, uint32_t)
uint32_t EmitFPFma32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPMin32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPMax32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPMinTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPMaxTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPMedTri32(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1, uint32_t arg2);
uint32_t EmitFPRecip32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPRecipIFlag32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPRecipSqrt32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPSqrt(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPExp2(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPLog2(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPLdexp(ValueEmitContext& ctx, uint32_t arg0, uint32_t arg1);
uint32_t EmitFPRoundEven32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPFloor32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPCeil32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPTrunc32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPFract32(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPSin(ValueEmitContext& ctx, uint32_t arg0);
uint32_t EmitFPCos(ValueEmitContext& ctx, uint32_t arg0);
#undef EMIT_NATIVE
uint32_t EmitIdentity(ValueEmitContext& ctx, uint32_t value);
void EmitVoid(ValueEmitContext&);
inline constexpr auto EmitReference = EmitVoid;
inline constexpr auto EmitReferenceU32 = EmitVoid;
inline constexpr auto EmitControlNop = EmitVoid;
inline constexpr auto EmitWaitcnt = EmitVoid;
inline constexpr auto EmitSendmsg = EmitVoid;
inline constexpr auto EmitTtraceData = EmitVoid;
inline constexpr auto EmitInstPrefetch = EmitVoid;
void EmitBarrier(ValueEmitContext& ctx);
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetUserData(ValueEmitContext& ctx, IR::ScalarReg reg);
uint32_t EmitGetBuiltin(ValueEmitContext& ctx, IR::Value kind, IR::Value index);
uint32_t EmitUndefU1(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitUndefU8 = EmitUndefU1;
inline constexpr auto EmitUndefU16 = EmitUndefU1;
inline constexpr auto EmitUndefU32 = EmitUndefU1;
inline constexpr auto EmitUndefU64 = EmitUndefU1;
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWqmU64(ValueEmitContext& ctx, uint32_t value);
uint32_t EmitLaneId(ValueEmitContext& ctx);
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
inline constexpr auto EmitGetSrtResource = EmitVoid;
inline constexpr auto EmitGetBufferResource = EmitGetSrtResource;
inline constexpr auto EmitGetAddressResource = EmitGetSrtResource;
inline constexpr auto EmitGetScratchResource = EmitGetSrtResource;
inline constexpr auto EmitGetImageResource = EmitGetSrtResource;
inline constexpr auto EmitGetSamplerResource = EmitGetSrtResource;
inline constexpr auto EmitMakeImageAddress = EmitGetSrtResource;
void EmitMemory(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitBpermuteU32 = EmitMemory;
inline constexpr auto EmitReadConst = EmitMemory;
inline constexpr auto EmitReadConstBuffer = EmitMemory;
inline constexpr auto EmitLoadAddressU8 = EmitMemory;
inline constexpr auto EmitLoadAddressU16 = EmitMemory;
inline constexpr auto EmitLoadAddressU32 = EmitMemory;
inline constexpr auto EmitStoreAddressU8 = EmitMemory;
inline constexpr auto EmitStoreAddressU16 = EmitMemory;
inline constexpr auto EmitStoreAddressU32 = EmitMemory;
inline constexpr auto EmitLoadBufferU8 = EmitMemory;
inline constexpr auto EmitLoadBufferU16 = EmitMemory;
inline constexpr auto EmitLoadBufferU32 = EmitMemory;
inline constexpr auto EmitLoadBufferU32x2 = EmitMemory;
inline constexpr auto EmitLoadBufferU32x3 = EmitMemory;
inline constexpr auto EmitLoadBufferU32x4 = EmitMemory;
inline constexpr auto EmitStoreBufferU8 = EmitMemory;
inline constexpr auto EmitStoreBufferU16 = EmitMemory;
inline constexpr auto EmitStoreBufferU32 = EmitMemory;
inline constexpr auto EmitStoreBufferU32x2 = EmitMemory;
inline constexpr auto EmitStoreBufferU32x3 = EmitMemory;
inline constexpr auto EmitStoreBufferU32x4 = EmitMemory;
inline constexpr auto EmitBufferAtomicSwap32 = EmitMemory;
inline constexpr auto EmitBufferAtomicCmpSwap32 = EmitMemory;
inline constexpr auto EmitBufferAtomicSwap64 = EmitMemory;
inline constexpr auto EmitBufferAtomicIAdd32 = EmitMemory;
inline constexpr auto EmitBufferAtomicISub32 = EmitMemory;
inline constexpr auto EmitBufferAtomicSMin32 = EmitMemory;
inline constexpr auto EmitBufferAtomicUMin32 = EmitMemory;
inline constexpr auto EmitBufferAtomicSMax32 = EmitMemory;
inline constexpr auto EmitBufferAtomicUMax32 = EmitMemory;
inline constexpr auto EmitBufferAtomicAnd32 = EmitMemory;
inline constexpr auto EmitBufferAtomicOr32 = EmitMemory;
inline constexpr auto EmitBufferAtomicOr64 = EmitMemory;
inline constexpr auto EmitBufferAtomicXor32 = EmitMemory;
inline constexpr auto EmitBufferAtomicFMin32 = EmitMemory;
inline constexpr auto EmitBufferAtomicFMax32 = EmitMemory;
inline constexpr auto EmitLoadSharedU8 = EmitMemory;
inline constexpr auto EmitLoadSharedU16 = EmitMemory;
inline constexpr auto EmitLoadSharedU32 = EmitMemory;
inline constexpr auto EmitLoadSharedU32x2 = EmitMemory;
inline constexpr auto EmitLoadSharedU32x3 = EmitMemory;
inline constexpr auto EmitLoadSharedU32x4 = EmitMemory;
inline constexpr auto EmitWriteSharedU8 = EmitMemory;
inline constexpr auto EmitWriteSharedU16 = EmitMemory;
inline constexpr auto EmitWriteSharedU32 = EmitMemory;
inline constexpr auto EmitWriteSharedU32x2 = EmitMemory;
inline constexpr auto EmitWriteSharedU32x3 = EmitMemory;
inline constexpr auto EmitWriteSharedU32x4 = EmitMemory;
inline constexpr auto EmitSharedAtomicFMin32 = EmitMemory;
inline constexpr auto EmitSharedAtomicFMax32 = EmitMemory;
inline constexpr auto EmitSharedAtomicSwap32 = EmitMemory;
inline constexpr auto EmitSharedAtomicIAdd32 = EmitMemory;
inline constexpr auto EmitSharedAtomicISub32 = EmitMemory;
inline constexpr auto EmitSharedAtomicInc32 = EmitMemory;
inline constexpr auto EmitSharedAtomicDec32 = EmitMemory;
inline constexpr auto EmitSharedAtomicSMin32 = EmitMemory;
inline constexpr auto EmitSharedAtomicUMin32 = EmitMemory;
inline constexpr auto EmitSharedAtomicSMax32 = EmitMemory;
inline constexpr auto EmitSharedAtomicUMax32 = EmitMemory;
inline constexpr auto EmitSharedAtomicAnd32 = EmitMemory;
inline constexpr auto EmitSharedAtomicOr32 = EmitMemory;
inline constexpr auto EmitSharedAtomicXor32 = EmitMemory;
inline constexpr auto EmitDataAppend = EmitMemory;
inline constexpr auto EmitDataConsume = EmitMemory;
inline constexpr auto EmitSwizzleU32 = EmitMemory;
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitImageQueryDimensions = EmitImage;
inline constexpr auto EmitImageQueryLod = EmitImage;
inline constexpr auto EmitImageRead = EmitImage;
inline constexpr auto EmitImageWrite = EmitImage;
inline constexpr auto EmitImageSampleRaw = EmitImage;
inline constexpr auto EmitImageGatherRaw = EmitImage;
inline constexpr auto EmitImageAtomicSwap32 = EmitImage;
inline constexpr auto EmitImageAtomicIAdd32 = EmitImage;
inline constexpr auto EmitImageAtomicUMin32 = EmitImage;
inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitPhi = EmitUnreachable;
inline constexpr auto EmitGetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitSetThreadBitScalarRegister = EmitUnreachable;
inline constexpr auto EmitGetScalarMaskTag = EmitUnreachable;
inline constexpr auto EmitSetScalarMaskTag = EmitUnreachable;
inline constexpr auto EmitGetScalarRegister = EmitUnreachable;
inline constexpr auto EmitSetScalarRegister = EmitUnreachable;
inline constexpr auto EmitGetVectorRegister = EmitUnreachable;
inline constexpr auto EmitSetVectorRegister = EmitUnreachable;
inline constexpr auto EmitGetGotoVariable = EmitUnreachable;
inline constexpr auto EmitSetGotoVariable = EmitUnreachable;
inline constexpr auto EmitGetScc = EmitUnreachable;
inline constexpr auto EmitSetScc = EmitUnreachable;
inline constexpr auto EmitGetExec = EmitUnreachable;
inline constexpr auto EmitSetExec = EmitUnreachable;
inline constexpr auto EmitGetExecLo = EmitUnreachable;
inline constexpr auto EmitSetExecLo = EmitUnreachable;
inline constexpr auto EmitGetExecHi = EmitUnreachable;
inline constexpr auto EmitSetExecHi = EmitUnreachable;
inline constexpr auto EmitGetVcc = EmitUnreachable;
inline constexpr auto EmitSetVcc = EmitUnreachable;
inline constexpr auto EmitGetVccLo = EmitUnreachable;
inline constexpr auto EmitSetVccLo = EmitUnreachable;
inline constexpr auto EmitGetVccHi = EmitUnreachable;
inline constexpr auto EmitSetVccHi = EmitUnreachable;
inline constexpr auto EmitGetM0 = EmitUnreachable;
inline constexpr auto EmitSetM0 = EmitUnreachable;
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -196,8 +196,6 @@ struct ValueEmitContext {
uint32_t Shuffle(const IR::Inst& inst, size_t index, uint32_t lane);
uint32_t Result(const IR::Inst& inst);
uint32_t TypeId(IR::Type type) const;
uint32_t Emit(const IR::Inst& inst, uint32_t opcode, IR::Type type,
std::initializer_list<uint32_t> args);
uint32_t Define(const IR::Inst& inst, uint32_t value);
uint32_t ResourceIndex(IR::Value value, IR::ValueOpcode opcode);
const IR::Inst* ImageAddress(IR::Value value);
@@ -490,14 +488,6 @@ uint32_t EmitFAbsValue(EmitterState& state, uint32_t value);
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state);
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
@@ -1044,7 +1044,7 @@ void DefineGetBdaPointer(EmitterState& state) {
state.builder.AddFunction({spv::OpFunctionEnd});
}
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
void EmitMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto op = inst.GetOpcode();
const auto buffer_components = IR::BufferComponentCount(op);
@@ -1052,11 +1052,11 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto access = IR::BufferAccessOf(op);
if (access == IR::BufferAccess::Read) {
ctx.Define(inst, LoadWideBuffer(ctx, inst, buffer_components));
return true;
return;
}
if (access == IR::BufferAccess::Write) {
StoreWideBuffer(ctx, inst, buffer_components);
return true;
return;
}
}
const auto shared_components = IR::SharedComponentCount(op);
@@ -1066,23 +1066,23 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
} else {
StoreWideShared(ctx, inst, shared_components);
}
return true;
return;
}
if ((op == IR::ValueOpcode::LoadAddressU32 || op == IR::ValueOpcode::ReadConstBuffer) &&
ctx.Memory(inst).planning_only) {
return true;
return;
}
if (op == IR::ValueOpcode::ReadConst) {
if (state.flattened_srt_variable == 0) {
ctx.Fail(inst, "requires the flattened SRT descriptor");
return true;
return;
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
ctx.Arg(inst, 1)});
ctx.Emit(inst, spv::OpLoad, IR::Type::U32, {pointer});
return true;
ctx.Define(inst, EmitNative<spv::OpLoad, IR::Type::U32>(ctx, pointer));
return;
}
if (op == IR::ValueOpcode::ReadConstBuffer) {
auto mem = ctx.Memory(inst);
@@ -1101,13 +1101,13 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitMemoryElementPointer(state, access, element)});
return value;
}));
return true;
return;
}
const auto address_info = IR::AddressOpcodeInfoOf(op);
const bool load_address = address_info.access == IR::AddressAccess::Read;
if (load_address && ctx.Memory(inst).kind != IR::ResourceKind::Scratch) {
ctx.Define(inst, LoadBda(ctx, inst, ctx.Memory(inst), address_info.data_bits));
return true;
return;
}
const bool load_buffer = op == IR::ValueOpcode::LoadBufferU8 ||
op == IR::ValueOpcode::LoadBufferU16 ||
@@ -1127,7 +1127,7 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
else
value = LoadWord(ctx, inst, mem);
ctx.Define(inst, value);
return true;
return;
}
const bool store_address = address_info.access == IR::AddressAccess::Write;
const bool store_buffer = op == IR::ValueOpcode::StoreBufferU8 ||
@@ -1146,35 +1146,35 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
StoreSubword(ctx, inst, mem, 16);
else
StoreWord(ctx, inst, mem);
return true;
return;
}
const auto atomic_opcode = SpirvAtomicOpcode(op);
if (atomic_opcode != 0 && inst.GetType() == IR::Type::U64) {
ctx.Define(inst, EmitBufferAtomic64(ctx, inst, ctx.Memory(inst)));
return true;
return;
}
if (atomic_opcode != 0) {
ctx.Define(inst, EmitAtomic(ctx, inst, ctx.Memory(inst)));
return true;
return;
}
if (op == IR::ValueOpcode::BufferAtomicFMin32 || op == IR::ValueOpcode::BufferAtomicFMax32) {
ctx.Define(inst, FloatAtomic(ctx, inst, ctx.Memory(inst),
op == IR::ValueOpcode::BufferAtomicFMax32));
return true;
return;
}
if (op == IR::ValueOpcode::SharedAtomicInc32 || op == IR::ValueOpcode::SharedAtomicDec32) {
const auto replacement = op == IR::ValueOpcode::SharedAtomicInc32
? AtomicIncrement : AtomicDecrement;
ctx.Define(inst, EmitAtomicUpdate(ctx, inst, ctx.Memory(inst), replacement));
return true;
return;
}
if (op == IR::ValueOpcode::SharedAtomicFMin32 || op == IR::ValueOpcode::SharedAtomicFMax32) {
SharedFloatAtomic(ctx, inst, ctx.Memory(inst), op == IR::ValueOpcode::SharedAtomicFMax32);
return true;
return;
}
if (op == IR::ValueOpcode::DataAppend || op == IR::ValueOpcode::DataConsume) {
ctx.Define(inst, AppendConsume(ctx, inst, op == IR::ValueOpcode::DataAppend));
return true;
return;
}
if (op == IR::ValueOpcode::SwizzleU32 || op == IR::ValueOpcode::BpermuteU32) {
uint32_t source = ctx.Arg(inst, 0);
@@ -1197,9 +1197,9 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
target = Binary(state, spv::OpBitwiseOr, TypeU32(state), base, index);
}
ctx.Define(inst, EmitDsMaskedLaneRead(state, source, target, ctx.Arg(inst, 2)));
return true;
return;
}
return false;
ctx.Fail(inst, "has no memory SPIR-V emitter");
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
#include "common/assert.h"
@@ -6,7 +6,9 @@
#include <bit>
#include <functional>
#include <optional>
#include <type_traits>
#include <unordered_set>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
@@ -227,12 +229,46 @@ uint32_t EmitDispatcherNextPc(ValueEmitContext& ctx, const DispatcherFunctionSta
}
}
void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
if (EmitValueFlow(ctx, inst) || EmitValueAlu(ctx, inst) || EmitValueMemory(ctx, inst) ||
EmitValueImage(ctx, inst)) {
return;
template <typename T>
decltype(auto) Arg(ValueEmitContext& ctx, const IR::Inst& inst, size_t index) {
if constexpr (std::is_same_v<T, const IR::Inst&>) {
return inst;
} else if constexpr (std::is_same_v<T, IR::Value>) {
return inst.Arg(index);
} else if constexpr (std::is_same_v<T, IR::ScalarReg>) {
return inst.Arg(index).ScalarRegister();
} else {
static_assert(std::is_same_v<T, uint32_t>);
return ctx.Def(inst.Arg(index));
}
}
template <typename Return, typename... Args>
void Invoke(Return (*emit)(ValueEmitContext&, Args...), ValueEmitContext& ctx,
const IR::Inst& inst) {
// A full instruction keeps metadata and predicated/lane operand loads lazy.
constexpr bool has_inst = (std::is_same_v<Args, const IR::Inst&> || ...);
[&]<size_t... I>(std::index_sequence<I...>) {
const auto call = [&] {
return emit(ctx, Arg<Args>(ctx, inst, I - (has_inst && I != 0))...);
};
if constexpr (std::is_void_v<Return>) {
call();
} else {
static_assert(std::is_same_v<Return, uint32_t>);
ctx.Define(inst, call());
}
}(std::index_sequence_for<Args...> {});
}
void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
switch (inst.GetOpcode()) {
#define VALUE_OPCODE(name, ...) \
case IR::ValueOpcode::name: return Invoke(Emit##name, ctx, inst);
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc"
#undef VALUE_OPCODE
default: ctx.Fail(inst, "has no direct SPIR-V emitter");
}
ctx.Fail(inst, "has no direct SPIR-V emitter");
}
void EmitStructuredInstruction(ValueEmitContext& ctx, StructuredFunctionState& structured,
@@ -534,16 +570,6 @@ uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
return id;
}
uint32_t ValueEmitContext::Emit(const IR::Inst& inst, uint32_t opcode, IR::Type type,
std::initializer_list<uint32_t> args) {
const auto type_id = TypeId(type);
const auto result = Result(inst);
std::vector<uint32_t> words {opcode, type_id, result};
words.insert(words.end(), args.begin(), args.end());
state.builder.AddFunction(words);
return result;
}
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
if (const auto found = definitions.find(&inst); found != definitions.end()) {
if (found->second != value) {