mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 12:26:51 +01:00
shader: clamp V_FRACT_F16/F32/F64 results below one
This commit is contained in:
@@ -56,6 +56,7 @@ EMIT_NATIVE(BitFieldSExtract, OpBitFieldSExtract, U32, uint32_t, uint32_t, uint3
|
|||||||
EMIT_NATIVE(SelectU1, OpSelect, U1, 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(SelectU32, OpSelect, U32, uint32_t, uint32_t, uint32_t)
|
||||||
EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t)
|
EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t)
|
||||||
|
EMIT_NATIVE(SelectF64, OpSelect, F64, uint32_t, uint32_t, uint32_t)
|
||||||
EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
|
EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
|
||||||
EMIT_NATIVE(ISub32, OpISub, 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(IMul32, OpIMul, U32, uint32_t, uint32_t)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <bit>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||||
@@ -118,6 +119,33 @@ void Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode op
|
|||||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
|
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Translator::V_FRACT(const Decoder::Instruction& inst, IR::Type type) {
|
||||||
|
// x - floor(x) rounds to 1.0 for a tiny negative x; v_fract returns the largest value below
|
||||||
|
// one instead. The ordered compare lets NaN through.
|
||||||
|
const auto destination = DestinationOperand(inst);
|
||||||
|
if (type == IR::Type::F64) {
|
||||||
|
const auto result = ir.Emit(IR::ValueOpcode::FPFract64, {ReadOperand(inst.src0, type)});
|
||||||
|
const auto below_one = ir.Emit(IR::ValueOpcode::BitCastF64U64,
|
||||||
|
{IR::Value(uint64_t {0x3fefffffffffffff})});
|
||||||
|
const auto above = ir.Emit(IR::ValueOpcode::FPOrdGreaterThan64, {result, below_one});
|
||||||
|
WriteOperand(destination, ir.Emit(IR::ValueOpcode::SelectF64, {above, below_one, result}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool half = type == IR::Type::F16;
|
||||||
|
const auto source = half ? ReadF16AsF32(inst.src0) : IR::F32(ReadOperand(inst.src0, type));
|
||||||
|
const auto result = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {source}));
|
||||||
|
// Clamp in the destination precision: 0x3f7fe000 is the half 0x3bff.
|
||||||
|
const auto below_one =
|
||||||
|
IR::F32(IR::Value::F32(std::bit_cast<float>(half ? 0x3f7fe000u : 0x3f7fffffu)));
|
||||||
|
const auto above = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThan32, {result, below_one}));
|
||||||
|
const auto clamped = SelectF32(above, below_one, result);
|
||||||
|
if (half) {
|
||||||
|
WriteF16(destination, clamped);
|
||||||
|
} else {
|
||||||
|
WriteOperand(destination, clamped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool reverse) {
|
bool reverse) {
|
||||||
std::array<IR::Value, 2> args;
|
std::array<IR::Value, 2> args;
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ private:
|
|||||||
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||||
bool mix);
|
bool mix);
|
||||||
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||||
|
void V_FRACT(const Decoder::Instruction& inst, IR::Type type);
|
||||||
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||||
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||||
bool mix);
|
bool mix);
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
|||||||
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false);
|
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false);
|
||||||
case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
|
case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
|
||||||
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false);
|
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false);
|
||||||
case O::V_FRACT_F16: return Float16Unary(inst, IR::ValueOpcode::FPFract32, false);
|
case O::V_FRACT_F16: return V_FRACT(inst, IR::Type::F16);
|
||||||
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin);
|
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin);
|
||||||
case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
|
case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
|
||||||
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
|
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
|
||||||
@@ -574,8 +574,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
|||||||
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
|
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
|
||||||
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64);
|
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64);
|
||||||
case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
|
case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
|
||||||
case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32);
|
case O::V_FRACT_F32: return V_FRACT(inst, IR::Type::F32);
|
||||||
case O::V_FRACT_F64: return FloatUnary(inst, IR::ValueOpcode::FPFract64);
|
case O::V_FRACT_F64: return V_FRACT(inst, IR::Type::F64);
|
||||||
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32);
|
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32);
|
||||||
case O::V_TRUNC_F64: return FloatUnary(inst, IR::ValueOpcode::FPTrunc64);
|
case O::V_TRUNC_F64: return FloatUnary(inst, IR::ValueOpcode::FPTrunc64);
|
||||||
case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32);
|
case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32);
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ VALUE_OPCODE(WqmU64, U64, U64)
|
|||||||
// Core value operations used to expand architectural side effects.
|
// Core value operations used to expand architectural side effects.
|
||||||
VALUE_OPCODE(SelectU1, U1, U1, U1, U1)
|
VALUE_OPCODE(SelectU1, U1, U1, U1, U1)
|
||||||
VALUE_OPCODE(SelectF32, F32, U1, F32, F32)
|
VALUE_OPCODE(SelectF32, F32, U1, F32, F32)
|
||||||
|
VALUE_OPCODE(SelectF64, F64, U1, F64, F64)
|
||||||
VALUE_OPCODE(IAdd32, U32, U32, U32)
|
VALUE_OPCODE(IAdd32, U32, U32, U32)
|
||||||
VALUE_OPCODE(IAdd64, U64, U64, U64)
|
VALUE_OPCODE(IAdd64, U64, U64, U64)
|
||||||
VALUE_OPCODE(ISub32, U32, U32, U32)
|
VALUE_OPCODE(ISub32, U32, U32, U32)
|
||||||
|
|||||||
@@ -27670,16 +27670,16 @@ TestCase VectorSpecialF16Ops() {
|
|||||||
TestCase VectorFractF16CapturedAndEdges() {
|
TestCase VectorFractF16CapturedAndEdges() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
// RDNA2 section 12.8: x - floor(x), rounded to half precision.
|
// x - floor(x) rounded to half precision, clamped to 0x3bff below one.
|
||||||
// Load at runtime to exercise the emitted operation, including half subnormals.
|
// Load at runtime to exercise the emitted operation, including half subnormals.
|
||||||
const std::array<u32, 18> inputs{
|
const std::array<u32, 18> inputs{
|
||||||
0x3d00u, 0xbd00u, 0x0000u, 0x8000u, 0x0001u, 0x8001u,
|
0x3d00u, 0xbd00u, 0x0000u, 0x8000u, 0x0001u, 0x8001u,
|
||||||
0x03ffu, 0x83ffu, 0x7bffu, 0xfbffu, 0x3800u, 0xb800u,
|
0x03ffu, 0x83ffu, 0x7bffu, 0xfbffu, 0x3800u, 0xb800u,
|
||||||
0x8c00u, 0x8c01u, 0x7c00u, 0xfc00u, 0x7e55u, 0x7d01u};
|
0x8c00u, 0x8c01u, 0x7c00u, 0xfc00u, 0x7e55u, 0x7d01u};
|
||||||
const std::array<u32, 18> fractions{
|
const std::array<u32, 18> fractions{
|
||||||
0x3400u, 0x3a00u, 0x0000u, 0x0000u, 0x0001u, 0x3c00u,
|
0x3400u, 0x3a00u, 0x0000u, 0x0000u, 0x0001u, 0x3bffu,
|
||||||
0x03ffu, 0x3c00u, 0x0000u, 0x0000u, 0x3800u, 0x3800u,
|
0x03ffu, 0x3bffu, 0x0000u, 0x0000u, 0x3800u, 0x3800u,
|
||||||
0x3c00u, 0x3bffu, 0x7e00u, 0x7e00u, 0x7e00u, 0x7e00u};
|
0x3bffu, 0x3bffu, 0x7e00u, 0x7e00u, 0x7e00u, 0x7e00u};
|
||||||
TestCase test;
|
TestCase test;
|
||||||
test.name = "VectorFractF16CapturedAndEdges";
|
test.name = "VectorFractF16CapturedAndEdges";
|
||||||
for (u32 bits : inputs) {
|
for (u32 bits : inputs) {
|
||||||
@@ -27710,6 +27710,37 @@ TestCase VectorFractF16CapturedAndEdges() {
|
|||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V_FRACT_F32 clamps to 0x3f7fffff where x - floor(x) rounds to 1.0, before output modifiers.
|
||||||
|
TestCase VectorFractF32Edges() {
|
||||||
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
|
constexpr std::array<std::array<u32, 3>, 4> cases{{
|
||||||
|
{0xb3800000u, 0, 0x3f7fffffu}, // -2^-24: exact
|
||||||
|
{0xb3000000u, 0, 0x3f7fffffu}, // -2^-25: the tie rounds to 1.0
|
||||||
|
{0xb3000000u, 1, 0x3fffffffu}, // omod *2 applies after the clamp
|
||||||
|
{0x7fc00001u, 0, 0x7fc00000u}}}; // Quiet NaN class
|
||||||
|
TestCase test;
|
||||||
|
test.name = "VectorFractF32Edges";
|
||||||
|
for (const auto &[source, omod, expected] : cases) test.initial.push_back(source);
|
||||||
|
test.expected = test.initial;
|
||||||
|
auto &code = test.code;
|
||||||
|
for (u32 i = 0; i < cases.size(); i++) {
|
||||||
|
AppendVMovU32(&code, 30, i * 4u);
|
||||||
|
AppendBufferLoadDword(&code, 7, 30);
|
||||||
|
AppendVop3(&code, 0x1a0, 9, Vgpr(7), 0, 0, 0, 0, false, cases[i][1]);
|
||||||
|
if (i + 1u == cases.size()) {
|
||||||
|
code.push_back(EncodeVop2(0x1b, 9, 255u, 9));
|
||||||
|
code.push_back(0x7fc00000u);
|
||||||
|
}
|
||||||
|
AppendStoreVgpr(&code, 9, static_cast<u32>(cases.size()) + i);
|
||||||
|
test.expected.push_back(cases[i][2]);
|
||||||
|
}
|
||||||
|
AppendEnd(&code);
|
||||||
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FRACT_F32,
|
||||||
|
O::V_AND_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
TestCase VectorFractF16Modifiers() {
|
TestCase VectorFractF16Modifiers() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
@@ -29628,7 +29659,7 @@ TestCase VectorFractF64CapturedAndEdges() {
|
|||||||
{0x4320000000000001ull, 0x3fe0000000000000ull}, // 2^51 + .5
|
{0x4320000000000001ull, 0x3fe0000000000000ull}, // 2^51 + .5
|
||||||
{0, 0}, {0x8000000000000000ull, 0},
|
{0, 0}, {0x8000000000000000ull, 0},
|
||||||
{0x401c000000000000ull, 0}, {0xc01c000000000000ull, 0},
|
{0x401c000000000000ull, 0}, {0xc01c000000000000ull, 0},
|
||||||
{1, 1}, {0x8000000000000001ull, 0x3ff0000000000000ull},
|
{1, 1}, {0x8000000000000001ull, 0x3fefffffffffffffull}, // Clamped below one.
|
||||||
{0x000fffffffffffffull, 0x000fffffffffffffull},
|
{0x000fffffffffffffull, 0x000fffffffffffffull},
|
||||||
{0x7fefffffffffffffull, 0},
|
{0x7fefffffffffffffull, 0},
|
||||||
{0x7ff0000000000000ull, 0x7ff8000000000000ull},
|
{0x7ff0000000000000ull, 0x7ff8000000000000ull},
|
||||||
@@ -44123,6 +44154,7 @@ std::vector<TestCase> MakeCases() {
|
|||||||
AddCase(VectorMinMaxMed3F16Ops);
|
AddCase(VectorMinMaxMed3F16Ops);
|
||||||
AddCase(VectorSpecialF16Ops);
|
AddCase(VectorSpecialF16Ops);
|
||||||
AddCase(VectorFractF16CapturedAndEdges);
|
AddCase(VectorFractF16CapturedAndEdges);
|
||||||
|
AddCase(VectorFractF32Edges);
|
||||||
AddCase(VectorFractF16Modifiers);
|
AddCase(VectorFractF16Modifiers);
|
||||||
AddCase(VectorCosF16CapturedSdwaAndEdges);
|
AddCase(VectorCosF16CapturedSdwaAndEdges);
|
||||||
AddCase(VectorSinF16SdwaAndEdges);
|
AddCase(VectorSinF16SdwaAndEdges);
|
||||||
@@ -50749,6 +50781,8 @@ int main(int argc, char **argv) {
|
|||||||
if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
RunCase(&vulkan, VectorFractF16CapturedAndEdges());
|
RunCase(&vulkan, VectorFractF16CapturedAndEdges());
|
||||||
|
RunCase(&vulkan, VectorFractF32Edges());
|
||||||
|
RunCase(&vulkan, VectorFractF64CapturedAndEdges());
|
||||||
RunCase(&vulkan, VectorFractF16Modifiers());
|
RunCase(&vulkan, VectorFractF16Modifiers());
|
||||||
RunCase(&vulkan, NativeAndSdwa16BitDestinationWrites());
|
RunCase(&vulkan, NativeAndSdwa16BitDestinationWrites());
|
||||||
RunCase(&vulkan, VectorSpecialF16Ops());
|
RunCase(&vulkan, VectorSpecialF16Ops());
|
||||||
|
|||||||
Reference in New Issue
Block a user