mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Shader: implement RDNA2 image atomic float maximum
Shader: implement RDNA2 image atomic float minimum Co-authored-by: foufouadi <attar.foued@gmail.com> Co-authored-by: frans <francois.att@gmail.com> Co-authored-by: fxpw <fxpw@outlook.com> Co-authored-by: Matthew Hurley <171001439+theantipopau@users.noreply.github.com>
This commit is contained in:
1 parent
fd2e15ee73
commit
cf268af839
11 files changed
+161
-6
No files matched your search
@@ -335,8 +335,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
||||
resource.atomic;
|
||||
const bool format_ok =
|
||||
raw_sint_storage ||
|
||||
raw_sint_storage || raw_float_atomic ||
|
||||
(numeric_class != Prospero::TextureNumericClass::Unsupported &&
|
||||
numeric_class != Prospero::TextureNumericClass::Sint &&
|
||||
uint_resource == (numeric_class == Prospero::TextureNumericClass::Uint) &&
|
||||
@@ -640,7 +642,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
pixel_format = depth_format->depth_attachment_format;
|
||||
}
|
||||
}
|
||||
const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt
|
||||
const auto storage_view_format = storage && (resource.atomic ||
|
||||
format == Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||
|
||||
@@ -877,7 +877,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return;
|
||||
}
|
||||
const auto atomic_opcode = ImageAtomicOpcode(op);
|
||||
if (atomic_opcode != spv::OpNop) {
|
||||
if (image_info.access == IR::ImageAccess::Atomic) {
|
||||
const auto dimension = image.dimension;
|
||||
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
@@ -887,6 +887,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
ConstantU32(state, 0));
|
||||
if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
|
||||
op == IR::ValueOpcode::ImageAtomicFMax32) {
|
||||
return AtomicUpdate(state, pointer, IR::ResourceKind::Image,
|
||||
[&](uint32_t old) {
|
||||
return EmitFloatAtomicReplacement(
|
||||
state, old, ctx.Arg(inst, 2),
|
||||
op == IR::ValueOpcode::ImageAtomicFMax32);
|
||||
});
|
||||
}
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(atomic_opcode, TypeU32(state), old, pointer,
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
|
||||
@@ -271,6 +271,8 @@ inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicFMin32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicFMax32 = EmitImage;
|
||||
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitPhi = EmitUnreachable;
|
||||
inline constexpr auto EmitTessellationBase = EmitUnreachable;
|
||||
|
||||
@@ -198,6 +198,8 @@ constexpr Detail::OpcodeMap MIMG_ATOMIC_OPCODE_LIST[] = {
|
||||
{0x18u, Opcode::IMAGE_ATOMIC_AND},
|
||||
{0x19u, Opcode::IMAGE_ATOMIC_OR},
|
||||
{0x1au, Opcode::IMAGE_ATOMIC_XOR},
|
||||
{0x1eu, Opcode::IMAGE_ATOMIC_FMIN},
|
||||
{0x1fu, Opcode::IMAGE_ATOMIC_FMAX},
|
||||
};
|
||||
|
||||
constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCODE_LIST);
|
||||
|
||||
@@ -579,6 +579,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::IMAGE_ATOMIC_AND:
|
||||
case Opcode::IMAGE_ATOMIC_OR:
|
||||
case Opcode::IMAGE_ATOMIC_XOR:
|
||||
case Opcode::IMAGE_ATOMIC_FMIN:
|
||||
case Opcode::IMAGE_ATOMIC_FMAX:
|
||||
case Opcode::IMAGE_LOAD:
|
||||
case Opcode::IMAGE_LOAD_MIP:
|
||||
case Opcode::IMAGE_GET_RESINFO:
|
||||
|
||||
@@ -571,6 +571,8 @@ enum class Opcode {
|
||||
IMAGE_ATOMIC_AND,
|
||||
IMAGE_ATOMIC_OR,
|
||||
IMAGE_ATOMIC_XOR,
|
||||
IMAGE_ATOMIC_FMIN,
|
||||
IMAGE_ATOMIC_FMAX,
|
||||
IMAGE_SAMPLE,
|
||||
IMAGE_GATHER4_L,
|
||||
IMAGE_GATHER4_LZ,
|
||||
|
||||
@@ -223,7 +223,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
const bool store_or_atomic = decoded.opcode == Decoder::Opcode::IMAGE_STORE ||
|
||||
decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP ||
|
||||
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_SWAP &&
|
||||
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR);
|
||||
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_FMAX);
|
||||
if (store_or_atomic) {
|
||||
return index == 0u ? decoded.dst : decoded.src0;
|
||||
}
|
||||
@@ -1026,6 +1026,10 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicOr32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_XOR:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicXor32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_FMIN:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMin32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_FMAX:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMax32);
|
||||
|
||||
case Decoder::Opcode::FLAT_LOAD_UBYTE:
|
||||
case Decoder::Opcode::FLAT_LOAD_SBYTE:
|
||||
|
||||
@@ -220,6 +220,8 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::ImageAtomicAnd32:
|
||||
case ValueOpcode::ImageAtomicOr32:
|
||||
case ValueOpcode::ImageAtomicXor32:
|
||||
case ValueOpcode::ImageAtomicFMin32:
|
||||
case ValueOpcode::ImageAtomicFMax32:
|
||||
return {ImageAccess::Atomic, ImageResourceClass::Storage, false};
|
||||
default: return {};
|
||||
}
|
||||
|
||||
@@ -284,6 +284,8 @@ VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(GetAttribute, U32, U32, U32)
|
||||
VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
|
||||
|
||||
@@ -485,7 +485,8 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt &&
|
||||
format != Prospero::BufferFormat::k32Float) {
|
||||
return SpecializationFail(
|
||||
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
@@ -514,7 +515,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
fmt::format("storage image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
if (raw_sint_storage) {
|
||||
if (raw_sint_storage || base.atomic) {
|
||||
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
}
|
||||
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
|
||||
|
||||
@@ -16634,6 +16634,8 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
||||
case Opcode::IMAGE_ATOMIC_AND:
|
||||
case Opcode::IMAGE_ATOMIC_OR:
|
||||
case Opcode::IMAGE_ATOMIC_XOR:
|
||||
case Opcode::IMAGE_ATOMIC_FMIN:
|
||||
case Opcode::IMAGE_ATOMIC_FMAX:
|
||||
case Opcode::IMAGE_SAMPLE:
|
||||
case Opcode::IMAGE_GATHER4_LZ:
|
||||
case Opcode::IMAGE_GATHER4_C:
|
||||
@@ -28139,6 +28141,124 @@ TestCase ImageAtomicVariants() {
|
||||
return test;
|
||||
}
|
||||
|
||||
template <bool max_value> TestCase ImageAtomicFloatGlcAndExec() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code;
|
||||
constexpr u32 atomic_word = max_value ? 0xf07c0108u : 0xf0780108u;
|
||||
code.push_back(EncodeSop1(0x04, 12, 126)); // Preserve active workgroup lanes.
|
||||
AppendVMovU32(&code, 8, 2);
|
||||
AppendVMovU32(&code, 9, 1);
|
||||
AppendVMovLiteral(&code, 5, 0x40000000u); // 2.0
|
||||
// FMAX uses the captured GLC=0 instruction word.
|
||||
code.insert(code.end(), {atomic_word, 0x00010508u});
|
||||
AppendStoreVgpr(&code, 5, 0);
|
||||
AppendVMovLiteral(&code, 5, max_value ? 0x40800000u : 0x3f800000u);
|
||||
code.insert(code.end(), {atomic_word | 0x2000u, 0x00010508u}); // GLC=1.
|
||||
AppendStoreVgpr(&code, 5, 1);
|
||||
AppendVMovLiteral(&code, 5,
|
||||
max_value ? 0x41000000u : 0x3f000000u); // Inactive lane.
|
||||
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
||||
code.insert(code.end(), {atomic_word | 0x2000u, 0x00010508u});
|
||||
code.push_back(EncodeSop1(0x04, 126, 12));
|
||||
AppendStoreVgpr(&code, 5, 2);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = max_value ? "ImageAtomicFMaxCapturedGlcAndExec"
|
||||
: "ImageAtomicFMinGlcAndExec";
|
||||
test.code = std::move(code);
|
||||
test.expected = {0x40000000u, 0x40000000u,
|
||||
max_value ? 0x41000000u : 0x3f000000u};
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B64,
|
||||
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
const std::array<u32, 8> descriptor = {
|
||||
0x504a7c00u, 0xc1600000u, 0x0086c0efu, 0x91b80924u,
|
||||
0x00000000u, 0x00700080u, 0x00000000u, 0x00000000u};
|
||||
std::copy_n(descriptor.begin(), 8, test.user_data.begin() + 4);
|
||||
test.has_user_data = true;
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.storage_image_r32ui[6] = max_value ? 0x3f800000u : 0x40800000u;
|
||||
test.expected_storage_image_r32ui = test.storage_image_r32ui;
|
||||
test.expected_storage_image_r32ui[6] = max_value ? 0x40800000u : 0x3f800000u;
|
||||
test.required_spirv = {"OpImageTexelPointer", "OpAtomicCompareExchange", "R32ui"};
|
||||
return test;
|
||||
}
|
||||
|
||||
template <bool max_value> TestCase ImageAtomicFloatSpecialValues() {
|
||||
using O = ShaderOpcode;
|
||||
// source, previous texel, expected minimum, expected maximum.
|
||||
const u32 cases[][4] = {
|
||||
{0x40000000u, 0x3f800000u, 0x3f800000u, 0x40000000u},
|
||||
{0xc0000000u, 0xbf800000u, 0xc0000000u, 0xbf800000u},
|
||||
{0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u},
|
||||
{0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u},
|
||||
{0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u},
|
||||
{0x7fcabcdeu, 0x3f800000u, 0x3f800000u, 0x3f800000u},
|
||||
{0x3f800000u, 0x7fa54321u, 0x7fa54321u, 0x7fa54321u},
|
||||
{0x7faabcdeu, 0x3f800000u, 0x3f800000u, 0x3f800000u},
|
||||
{0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u},
|
||||
{0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
||||
{0x80000000u, 0x80000001u, 0x80000001u, 0x80000000u},
|
||||
{0x80000001u, 0x80000000u, 0x80000001u, 0x80000000u},
|
||||
{0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u},
|
||||
{0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u},
|
||||
};
|
||||
TestCase test;
|
||||
test.name = max_value ? "ImageAtomicFMaxSpecialValues"
|
||||
: "ImageAtomicFMinSpecialValues";
|
||||
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
||||
test.has_user_data = true;
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.expected_storage_image_r32ui = test.storage_image_r32ui;
|
||||
for (u32 i = 0; i < std::size(cases); i++) {
|
||||
AppendVMovU32(&test.code, 20, i & 3u);
|
||||
AppendVMovU32(&test.code, 21, i >> 2u);
|
||||
AppendVMovLiteral(&test.code, 0, cases[i][0]);
|
||||
test.code.push_back(EncodeMimg0(max_value ? 0x1f : 0x1e, 1, 0, true));
|
||||
test.code.push_back(EncodeMimg1(0, 20));
|
||||
AppendStoreVgpr(&test.code, 0, i);
|
||||
test.expected.push_back(cases[i][1]);
|
||||
test.storage_image_r32ui[i] = cases[i][1];
|
||||
test.expected_storage_image_r32ui[i] = cases[i][max_value ? 3 : 2];
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
test.opcodes = {O::V_MOV_B32,
|
||||
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
template <bool max_value> TestCase ImageAtomicFloatContendedWorkgroup() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = max_value ? "ImageAtomicFMaxContendedWorkgroup"
|
||||
: "ImageAtomicFMinContendedWorkgroup";
|
||||
test.code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // Float thread_id.x.
|
||||
AppendVMovU32(&test.code, 20, 0);
|
||||
AppendVMovU32(&test.code, 21, 0);
|
||||
test.code.push_back(EncodeMimg0(max_value ? 0x1f : 0x1e, 1));
|
||||
test.code.push_back(EncodeMimg1(1, 20));
|
||||
AppendEnd(&test.code);
|
||||
test.opcodes = {O::V_CVT_F32_U32, O::V_MOV_B32,
|
||||
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
|
||||
O::S_ENDPGM};
|
||||
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
||||
test.has_user_data = true;
|
||||
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
||||
test.storage_image_r32ui[0] =
|
||||
max_value ? 0xc2c80000u : 0x42c80000u; // -/+100.0
|
||||
test.expected_storage_image_r32ui = test.storage_image_r32ui;
|
||||
test.expected_storage_image_r32ui[0] =
|
||||
max_value ? 0x427c0000u : 0u; // 63.0 / 0.0
|
||||
test.compute_info.threads_num[0] = 64;
|
||||
test.compute_info.threads_num[1] = 1;
|
||||
test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageAtomicGlc0DoesNotReturnOldValue() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -28951,6 +29071,12 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageAtomicSwapReturnsPreviousTexel);
|
||||
AddCase(ImageStoreAndAtomicUseSeparateBindings);
|
||||
AddCase(ImageAtomicVariants);
|
||||
AddCase(ImageAtomicFloatGlcAndExec<true>);
|
||||
AddCase(ImageAtomicFloatGlcAndExec<false>);
|
||||
AddCase(ImageAtomicFloatSpecialValues<true>);
|
||||
AddCase(ImageAtomicFloatSpecialValues<false>);
|
||||
AddCase(ImageAtomicFloatContendedWorkgroup<true>);
|
||||
AddCase(ImageAtomicFloatContendedWorkgroup<false>);
|
||||
AddCase(ImageAtomicGlc0DoesNotReturnOldValue);
|
||||
AddCase(MultipleWorkitemsGlobalId);
|
||||
AddCase(DispatcherIrreducibleControlFlow);
|
||||
|
||||
Reference in new issue
Block a user