Merge pull request #697 from queeeef/feat/interp-f16

feat(shader): implement v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16
This commit is contained in:
Adrià 2026-10-08 23:56:17 +02:00 committed by GitHub
commit ae91d9780b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 796 additions and 6 deletions

View File

@ -612,6 +612,7 @@ target_include_directories(agc_driver_prepared_exports_tests PRIVATE $<TARGET_PR
target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(agc_driver_interpolation_modes_tests PRIVATE shader_recompiler_agc_driver)
target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>

View File

@ -1120,6 +1120,8 @@ ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list<std::uint
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.target.fragmentShaderBarycentricEnabled = true;
static constexpr std::array<std::uint32_t, 1> capabilities{spv::CapabilityFloat64};
request.target.supportedCapabilities = capabilities;
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
return Recompile(request);
@ -1172,6 +1174,19 @@ void verifyPixelParameterSlots() {
expectFailure([] { static_cast<void>(recompileSlots({0x423u, 0x3u}, shared)); }, "passes its vertices through unchanged", "an interpolated pass-through input was accepted");
}
void verifyF16PixelParameterSlots() {
static constexpr std::array<std::uint32_t, 7> high{0xd7420002u, 0x00020100u, 0xd75a0003u, 0x040a0300u, 0xf800180fu, 0x03030303u, 0xbf810000u};
static constexpr std::array<std::uint32_t, 7> low{0xd7420002u, 0x00020000u, 0xd75a0003u, 0x040a0200u, 0xf800180fu, 0x03030303u, 0xbf810000u};
auto inputs = slotInputs({0x03080003u}, high);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "a 16-bit interpolated input was not read per vertex at its slot");
inputs = slotInputs({0x03080023u}, high);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "an input with a defaulted low half was not read per vertex for its high half");
require(slotInputs({0x03180023u}, high).empty(), "an input whose high half is defaulted was declared for a high-half read");
require(slotInputs({0x03080023u}, low).empty(), "an input whose low half is defaulted was declared for a low-half read");
inputs = slotInputs({0x03180003u}, low);
require(inputs.size() == 1u && inputs[0].first == 3u && inputs[0].second, "an input with a defaulted high half was not read per vertex for its low half");
}
}
void verifyComputedTexelOffsets() {
@ -1769,6 +1784,7 @@ int main(int argc, char** argv) {
verifyLegacyPixelRequests();
verifyPixelExportReplay();
verifyPixelParameterSlots();
verifyF16PixelParameterSlots();
verifyComputedTexelOffsets();
verifyShaderClockScopes();
verifyUnnormalizedSamplers();

View File

@ -0,0 +1,308 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::PixelInput;
using ShaderRecompiler::PixelInputBit;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Sentinel = 0xdeadbeefu;
constexpr std::uint32_t Width = 192;
constexpr std::uint32_t Height = 128;
constexpr std::uint32_t ResultDwords = 12;
constexpr std::uint32_t Fp16Input = 0x03080000u;
constexpr std::uint32_t Fp16DefaultHighInput = 0x01580000u;
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
alignas(256) std::array<std::uint32_t, Width * Height * ResultDwords> Results{};
alignas(256) constexpr std::array<std::uint32_t, 10> VertexCode{
0xe0382000u, 0x80000005u, 0xe0382010u, 0x80000605u, 0xbf8c3f70u, 0xf800020fu, 0x09080706u, 0xf80008cfu, 0x03020100u, 0xbf810000u,
};
alignas(256) constexpr std::array<std::uint32_t, 47> PixelCode{
0x7e080f02u, 0x7e0a0f03u, 0xd5430006u, 0x0411ff05u, 0x000000c0u, 0x7e300300u, 0x7e320301u, 0xd742001au,
0x00020000u, 0x7e3602ffu, 0xabcd0000u, 0xd75a001bu, 0x046a0200u, 0xd7420008u, 0x00020100u, 0x7e3802ffu,
0x12340000u, 0xd75a001cu, 0x04220300u, 0x7e1e02ffu, 0x4500bc00u, 0xd743001du, 0x043e0140u, 0x7e3c0280u,
0xd75a001eu, 0x04760340u, 0x7e3e0280u, 0xd75a801fu, 0x046a0200u, 0xd7420020u, 0x00020181u, 0xd7420021u,
0x000201c1u, 0xd7420022u, 0x40020000u, 0xd7430023u, 0x043e0000u, 0xe0782000u, 0x80011806u, 0xe0782010u,
0x80011c06u, 0xe0782020u, 0x80012006u, 0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u,
};
constexpr std::array<std::uint32_t, 3> LowX{0x3400u, 0x3f00u, 0xb800u};
constexpr std::array<std::uint32_t, 3> HighX{0x4200u, 0xc100u, 0x4700u};
constexpr std::array<std::uint32_t, 3> LowY{0x3800u, 0x3a00u, 0x3000u};
constexpr std::array<std::uint32_t, 3> HighY{0x6c00u, 0x3c00u, 0xc800u};
struct Vertex {
std::array<float, 4> position;
std::array<std::uint32_t, 4> parameter;
};
float HalfToFloat(std::uint32_t half) {
const auto sign = (half & 0x8000u) != 0u ? -1.0f : 1.0f;
const auto exponent = static_cast<int>((half >> 10u) & 0x1fu);
const auto mantissa = static_cast<float>(half & 0x3ffu);
if (exponent == 0) return sign * std::ldexp(mantissa, -24);
if (exponent == 31) return mantissa == 0.0f ? sign * INFINITY : NAN;
return sign * std::ldexp(1024.0f + mantissa, exponent - 25);
}
std::uint32_t FloatToHalf(float value) {
const auto bits = std::bit_cast<std::uint32_t>(value);
const auto sign = (bits >> 16u) & 0x8000u;
const auto magnitude = bits & 0x7fffffffu;
if (magnitude > 0x7f800000u) return sign | 0x7e00u;
if (magnitude >= 0x477ff000u) return sign | 0x7c00u;
if (magnitude < 0x38800000u) {
const auto scaled = std::fabs(value) * 16777216.0f;
return sign | static_cast<std::uint32_t>(std::nearbyint(scaled));
}
const auto rounded = magnitude + 0xfffu + ((magnitude >> 13u) & 1u);
return sign | ((rounded - 0x38000000u) >> 13u);
}
float RoundToHalf(float value) {
return HalfToFloat(FloatToHalf(value));
}
std::uint32_t Pack(std::uint32_t low, std::uint32_t high) {
return low | (high << 16u);
}
std::vector<Vertex> Triangles() {
std::vector<Vertex> vertices;
constexpr std::uint32_t columns = 6;
constexpr std::uint32_t rows = 4;
for (std::uint32_t cell = 0; cell < columns * rows; ++cell) {
const float width = 2.0f / columns;
const float height = 2.0f / rows;
const float x = -1.0f + width * static_cast<float>(cell % columns);
const float y = -1.0f + height * static_cast<float>(cell / columns);
const float skew = 0.07f * static_cast<float>(cell % 5u);
const std::array<std::array<float, 2>, 3> corners{{
{x + (0.05f + skew) * width, y + 0.05f * height},
{x + 0.93f * width, y + (0.11f + skew) * height},
{x + (0.37f - skew * 0.5f) * width, y + 0.91f * height},
}};
for (std::uint32_t vertex = 0; vertex < 3u; ++vertex) {
vertices.push_back({{corners[vertex][0], corners[vertex][1], 0.5f, 1.0f}, {Pack(LowX[vertex], HighX[vertex]), Pack(LowY[vertex], HighY[vertex]), 0x3c003c00u, 0x3c003c00u}});
}
}
return vertices;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t stride, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
}
ShaderRecompiler::ShaderPixelStageInfo PixelStage(std::uint32_t waveSize, std::uint32_t input0) {
ShaderRecompiler::ShaderPixelStageInfo pixel{};
pixel.wave32 = waveSize == 32u;
pixel.interpolatorCount = 2;
pixel.interpolatorSettings[0] = input0;
pixel.interpolatorSettings[1] = Fp16DefaultHighInput;
pixel.inputAddr = PixelInputBit(PixelInput::PerspectiveCenter) | PixelInputBit(PixelInput::PositionX) | PixelInputBit(PixelInput::PositionY);
pixel.hasPerspectiveCenterVgpr = true;
pixel.posX = true;
pixel.posY = true;
pixel.targetOutputMode[0] = 9;
pixel.targetExportMapping.fill(0xe4u);
return pixel;
}
ShaderRecompiler::RecompileResult RecompilePixel(std::span<const std::uint32_t> code, std::uint32_t waveSize, std::uint32_t input0, const ShaderRecompiler::SpirvTarget& target, std::uint32_t pushOffset) {
std::vector<std::uint32_t> pixelUserData(8, 0u);
const auto results = BufferDescriptor(Results.data(), ResultDwords * 4u, Width * Height);
std::copy(results.begin(), results.end(), pixelUserData.begin() + 4);
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{waveSize, 0, pixelUserData, std::nullopt, PixelStage(waveSize, input0), std::nullopt, pixelMemory},
target,
{0, 0, pushOffset, 128 - pushOffset}
};
fragment.useCache = false;
fragment.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false};
return ShaderRecompiler::Recompile(fragment);
}
void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) {
static const auto triangles = Triangles();
const auto target = device.Target();
Pixels.fill(std::byte{0});
Results.fill(Sentinel);
std::vector<std::uint32_t> vertexUserData(4, 0u);
const auto vertexBuffer = BufferDescriptor(triangles.data(), sizeof(Vertex), static_cast<std::uint32_t>(triangles.size()));
std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin());
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
ShaderRecompiler::RecompileRequest vertex{
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
{waveSize, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory},
target,
{0, 0, 0, 64}
};
vertex.useCache = false;
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
const auto pixelResult = RecompilePixel(PixelCode, waveSize, Fp16Input, target, vertexPush);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Vertex, &vertexResult, 0},
{ShaderStage::Fragment, &pixelResult, vertexPush}
}};
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
state.colors = {state.color};
state.hasColorTarget = true;
state.renderExtent = {Width, Height};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
state.cullMode = VK_CULL_MODE_NONE;
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
state.blends = {state.blend};
state.blendConstants = {};
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(triangles.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
device.WaitIdle();
}
float Delta(const std::array<std::uint32_t, 3>& values, std::uint32_t vertex) {
return RoundToHalf(HalfToFloat(values[vertex]) - HalfToFloat(values[0]));
}
float FirstStep(float delta, float coordinate, float origin) {
return std::fma(delta, coordinate, origin);
}
double SecondStep(float delta, float coordinate, float firstStep) {
const double product = static_cast<double>(delta) * static_cast<double>(coordinate);
const double sum = product + firstStep;
const double productPart = sum - firstStep;
const double error = (product - productPart) + (firstStep - (sum - productPart));
if (error == 0.0 || (std::bit_cast<std::uint64_t>(sum) & 1u) != 0u) return sum;
return std::nextafter(sum, error > 0.0 ? INFINITY : -INFINITY);
}
double Saturate(double value) {
return value > 0.0 ? std::min(value, 1.0) : 0.0;
}
std::uint32_t HalfOf(double value) {
if (value == 0.0) return std::signbit(value) ? 0x8000u : 0u;
const int step = std::max(std::ilogb(value) - 10, -24);
const double rounded = std::ldexp(std::nearbyint(std::ldexp(value, -step)), step);
if (std::fabs(rounded) > 65504.0) return std::signbit(value) ? 0xfc00u : 0x7c00u;
if (rounded == 0.0) return std::signbit(value) ? 0x8000u : 0u;
return FloatToHalf(static_cast<float>(rounded));
}
void Check(std::uint32_t waveSize) {
const auto what = "16-bit interpolation wave" + std::to_string(waveSize);
std::uint32_t covered = 0;
for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
const auto* result = &Results[pixel * ResultDwords];
if (std::to_integer<std::uint8_t>(Pixels[pixel * 4u]) != 255u) {
Require(result[0] == Sentinel, what + ": uncovered pixel " + std::to_string(pixel) + " stored a result");
continue;
}
++covered;
const float i = std::bit_cast<float>(result[0]);
const float j = std::bit_cast<float>(result[1]);
const auto lowX = FirstStep(Delta(LowX, 1), i, HalfToFloat(LowX[0]));
const auto highX = FirstStep(Delta(HighX, 1), i, HalfToFloat(HighX[0]));
const auto highY = FirstStep(Delta(HighY, 1), i, HalfToFloat(0x4500u));
const std::array<std::uint32_t, ResultDwords> expected{
result[0],
result[1],
std::bit_cast<std::uint32_t>(lowX),
0xabcd0000u | HalfOf(SecondStep(Delta(LowX, 2), j, lowX)),
0x12340000u | HalfOf(SecondStep(Delta(HighX, 2), j, highX)),
std::bit_cast<std::uint32_t>(highY),
HalfOf(SecondStep(Delta(HighY, 2), j, highY)),
HalfOf(Saturate(SecondStep(Delta(LowX, 2), j, lowX))),
std::bit_cast<std::uint32_t>(FirstStep(0.0f, i, 1.0f)),
std::bit_cast<std::uint32_t>(FirstStep(0.0f, i, 0.0f)),
std::bit_cast<std::uint32_t>(FirstStep(Delta(LowX, 1), -i, HalfToFloat(LowX[0]))),
std::bit_cast<std::uint32_t>(FirstStep(Delta(LowX, 1), i, -1.0f)),
};
for (std::uint32_t dword = 2; dword < ResultDwords; ++dword) {
char text[160];
std::snprintf(text, sizeof(text), ": pixel %u dword %u is 0x%08x, expected 0x%08x (I %a, J %a)", pixel, dword, result[dword], expected[dword], static_cast<double>(i), static_cast<double>(j));
Require(result[dword] == expected[dword], what + text);
}
}
Require(covered > 1000, what + ": the triangles covered only " + std::to_string(covered) + " pixels");
}
ShaderRecompiler::SpirvTarget OfflineTarget(bool barycentric) {
ShaderRecompiler::SpirvTarget target{};
target.vulkanVersion = 0x00401000u;
target.spirvVersion = 0x00010300u;
target.subgroupSize = 64;
target.fragmentShaderBarycentricEnabled = barycentric;
return target;
}
void ExpectFailure(std::span<const std::uint32_t> code, std::uint32_t input0, bool barycentric, const char* expected, const char* message) {
try {
static_cast<void>(RecompilePixel(code, 64u, input0, OfflineTarget(barycentric), 0u));
} catch (const std::exception& error) {
Require(std::string(error.what()).find(expected) != std::string::npos, std::string(message) + ": " + error.what());
return;
}
throw std::runtime_error(message);
}
void ExpectRejected(std::uint32_t word0, std::uint32_t word1, const char* expected, const char* message) {
alignas(256) const std::array<std::uint32_t, 3> code{word0, word1, 0xbf810000u};
ExpectFailure(code, Fp16Input, true, expected, message);
}
}
int main() {
try {
ExpectFailure(PixelCode, Fp16Input, false, "requires fragmentShaderBarycentric", "16-bit interpolation was accepted without barycentrics");
ExpectFailure(PixelCode, 0x03000000u, true, "without FP16_INTERP_MODE", "16-bit interpolation of a 32-bit input was accepted");
ExpectFailure(PixelCode, 0x03080420u, true, "passes its vertices through unchanged", "16-bit interpolation of a pass-through input was accepted");
ExpectRejected(0xd75a001bu, 0x0c6a0200u, "interpolation modifiers", "v_interp_p2_f16 accepted an output modifier");
ExpectRejected(0xd75a081bu, 0x046a0200u, "interpolation modifiers", "v_interp_p2_f16 accepted op_sel");
ExpectRejected(0xd742001au, 0x20020000u, "interpolation modifiers", "v_interp_p1ll_f16 accepted a negated attribute");
ExpectRejected(0xd742001au, 0x00000000u, "not a vector register", "v_interp_p1ll_f16 accepted a scalar I coordinate");
ExpectRejected(0xd743001du, 0x003e0140u, "not a vector register", "v_interp_p1lv_f16 accepted a scalar P0");
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (!device->Target().fragmentShaderBarycentricEnabled) {
std::puts("skipped, the device has no fragmentShaderBarycentric");
return VulkanTestSkipped;
}
for (const auto waveSize : {64u, 32u}) {
Draw(*device, waveSize);
Check(waveSize);
}
std::puts("16-bit interpolation tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -0,0 +1,171 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "SpirvBackend/SpirvEmitterInstructions.hpp"
#include "SpirvBackend/SpirvEmitterHelpers.hpp"
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <string>
namespace {
using namespace ShaderRecompiler;
using AgcDriver::Graphics::Require;
struct Row {
std::array<std::uint32_t, 9> input;
std::array<std::array<std::uint32_t, 3>, 8> expected;
};
constexpr std::array<const char*, 8> ModeNames{"IEEE=0 f32-flush f16-flush", "IEEE=0 f32-flush f16-keep", "IEEE=0 f32-keep f16-flush", "IEEE=0 f32-keep f16-keep",
"IEEE=1 f32-flush f16-flush", "IEEE=1 f32-flush f16-keep", "IEEE=1 f32-keep f16-flush", "IEEE=1 f32-keep f16-keep"};
constexpr std::array<std::uint32_t, 8> ModeFlags{
InterpolationFlush32 | InterpolationFlush16, InterpolationFlush32, InterpolationFlush16, 0u,
InterpolationQuiet | InterpolationFlush32 | InterpolationFlush16, InterpolationQuiet | InterpolationFlush32, InterpolationQuiet | InterpolationFlush16, InterpolationQuiet};
constexpr std::array<const char*, 3> OpNames{"v_interp_p1ll_f16", "v_interp_p2_f16", "v_interp_p1lv_f16"};
constexpr std::array<Row, 32> Rows{{
{{0x33800000u, 0x7fa00000u, 0x43000000u, 0x33800000u, 0x4f000000u, 0xabcd0001u, 0x33800000u, 0x7fa00000u, 0x33800000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00005800u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00005800u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00005800u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00005800u, 0x7fe00000u}}}},
{{0xbf800000u, 0x00000001u, 0x7fa00000u, 0xbf800000u, 0x477ff000u, 0xabcd8200u, 0xbf800000u, 0x00000001u, 0xb8000000u}, {{{0x7fa00000u, 0x0000fc00u, 0x80000000u}, {0x7fa00000u, 0x0000fc00u, 0xb8000000u}, {0x7fa00000u, 0x0000fc00u, 0x80000001u}, {0x7fa00000u, 0x0000fc00u, 0xb8000000u}, {0x7fe00000u, 0x0000fc00u, 0x80000000u}, {0x7fe00000u, 0x0000fc00u, 0xb8000000u}, {0x7fe00000u, 0x0000fc00u, 0x80000001u}, {0x7fe00000u, 0x0000fc00u, 0xb8000000u}}}},
{{0x33800000u, 0x7fa00000u, 0xff800000u, 0x33800000u, 0x7f7fffffu, 0xabcdbc00u, 0x33800000u, 0x7fa00000u, 0xbf800000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00007c00u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00007c00u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00007c00u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00007c00u, 0x7fe00000u}}}},
{{0xb8000000u, 0x7fa00000u, 0x80000000u, 0xb8000000u, 0x7f7fffffu, 0xabcdbc00u, 0xb8000000u, 0x7fa00000u, 0xbf800000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0x33800000u, 0xff800000u, 0xff800000u, 0x33800000u, 0x7f800000u, 0xabcd0000u, 0x33800000u, 0xff800000u, 0x00000000u}, {{{0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xff800000u, 0x00007c00u, 0xff800000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xff800000u, 0x00007c00u, 0xff800000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xff800000u, 0x00007c00u, 0xff800000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xff800000u, 0x00007c00u, 0xff800000u}}}},
{{0x33800000u, 0x7f800000u, 0xffc02000u, 0x33800000u, 0x47800000u, 0xabcd8000u, 0x33800000u, 0x7f800000u, 0x80000000u}, {{{0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc02000u, 0x00001c00u, 0x7f800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc02000u, 0x00001c00u, 0x7f800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc02000u, 0x00001c00u, 0x7f800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc02000u, 0x00001c00u, 0x7f800000u}}}},
{{0xb8000000u, 0x477ff000u, 0x00000000u, 0xb8000000u, 0x4f000000u, 0xabcd5800u, 0xb8000000u, 0x477ff000u, 0x43000000u}, {{{0x00000000u, 0x00008000u, 0x43000000u}, {0xbffff000u, 0x0000fc00u, 0x42fc0040u}, {0x00000000u, 0x00008000u, 0x43000000u}, {0xbffff000u, 0x0000fc00u, 0x42fc0040u}, {0x00000000u, 0x00008000u, 0x43000000u}, {0xbffff000u, 0x0000fc00u, 0x42fc0040u}, {0x00000000u, 0x00008000u, 0x43000000u}, {0xbffff000u, 0x0000fc00u, 0x42fc0040u}}}},
{{0x33800000u, 0x7fa00000u, 0x477fe000u, 0x33800000u, 0x47800000u, 0xabcd7d00u, 0x33800000u, 0x7fa00000u, 0x7fa00000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00001c00u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00001c00u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00001c00u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00001c00u, 0x7fe00000u}}}},
{{0xb8000000u, 0x47800000u, 0x7fa00000u, 0xb8000000u, 0x7f7fffffu, 0xabcd7bffu, 0xb8000000u, 0x47800000u, 0x477fe000u}, {{{0x7fa00000u, 0x00008000u, 0x477fe000u}, {0x7fa00000u, 0x0000fc00u, 0x477fde00u}, {0x7fa00000u, 0x00008000u, 0x477fe000u}, {0x7fa00000u, 0x0000fc00u, 0x477fde00u}, {0x7fe00000u, 0x00008000u, 0x477fe000u}, {0x7fe00000u, 0x0000fc00u, 0x477fde00u}, {0x7fe00000u, 0x00008000u, 0x477fe000u}, {0x7fe00000u, 0x0000fc00u, 0x477fde00u}}}},
{{0x33800000u, 0xff800000u, 0xbf800000u, 0x33800000u, 0x3f800000u, 0xabcd7bffu, 0x33800000u, 0xff800000u, 0x477fe000u}, {{{0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00000001u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00000001u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00000001u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00000001u, 0xff800000u}}}},
{{0xb8000000u, 0x477ff000u, 0x33800000u, 0xb8000000u, 0x3f800000u, 0xabcd8200u, 0xb8000000u, 0x477ff000u, 0xb8000000u}, {{{0x00000000u, 0x00008000u, 0x80000000u}, {0xbffff000u, 0x00008200u, 0xbffff100u}, {0x00000000u, 0x00008000u, 0x80000000u}, {0xbffff000u, 0x00008200u, 0xbffff100u}, {0x00000000u, 0x00008000u, 0x80000000u}, {0xbffff000u, 0x00008200u, 0xbffff100u}, {0x00000000u, 0x00008000u, 0x80000000u}, {0xbffff000u, 0x00008200u, 0xbffff100u}}}},
{{0x33800000u, 0x7fa00000u, 0xb8000000u, 0x33800000u, 0xff800000u, 0xabcd7bffu, 0x33800000u, 0x7fa00000u, 0x477fe000u}, {{{0x7fa00000u, 0x0000fe00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fe00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fe00000u, 0x0000fe00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fe00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0xff800000u, 0x00000001u, 0x43000000u, 0xff800000u, 0x47800000u, 0xabcd7d00u, 0xff800000u, 0x00000001u, 0x7fa00000u}, {{{0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xff800000u, 0x0000fc00u, 0x7fa00000u}, {0xff800000u, 0x0000fc00u, 0x7fa00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xff800000u, 0x0000fc00u, 0x7fe00000u}, {0xff800000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0x7f800000u, 0x7fa00000u, 0x33800000u, 0x7f800000u, 0x80400000u, 0xabcd3c00u, 0x7f800000u, 0x7fa00000u, 0x3f800000u}, {{{0x7fa00000u, 0x0000fe00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fe00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fe00000u, 0x0000fe00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fe00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0x3f800000u, 0x00000001u, 0x7fa00000u, 0x3f800000u, 0xff800000u, 0xabcd0001u, 0x3f800000u, 0x00000001u, 0x33800000u}, {{{0x7fa00000u, 0x0000fc00u, 0x00000000u}, {0x7fa00000u, 0x0000fc00u, 0x33800000u}, {0x7fa00000u, 0x0000fc00u, 0x00000001u}, {0x7fa00000u, 0x0000fc00u, 0x33800000u}, {0x7fe00000u, 0x0000fc00u, 0x00000000u}, {0x7fe00000u, 0x0000fc00u, 0x33800000u}, {0x7fe00000u, 0x0000fc00u, 0x00000001u}, {0x7fe00000u, 0x0000fc00u, 0x33800000u}}}},
{{0xb8000000u, 0x7fa00000u, 0x33800000u, 0xb8000000u, 0x4f000000u, 0xabcd7e00u, 0xb8000000u, 0x7fa00000u, 0x7fc00000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x0000fc00u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0xff800000u, 0x80400000u, 0x43000000u, 0xff800000u, 0x477ff000u, 0xabcd7d00u, 0xff800000u, 0x80400000u, 0x7fa00000u}, {{{0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0x7f800000u, 0x0000fc00u, 0x7fa00000u}, {0x7f800000u, 0x0000fc00u, 0x7fa00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0xffc00000u, 0x0000fc00u, 0xffc00000u}, {0x7f800000u, 0x0000fc00u, 0x7fe00000u}, {0x7f800000u, 0x0000fc00u, 0x7fe00000u}}}},
{{0x33800000u, 0x4f000000u, 0x3f800000u, 0x33800000u, 0x47800000u, 0xabcdbc00u, 0x33800000u, 0x4f000000u, 0xbf800000u}, {{{0x3f800000u, 0x00008000u, 0xbf800000u}, {0x43010000u, 0x00001c00u, 0x42fe0000u}, {0x3f800000u, 0x00008000u, 0xbf800000u}, {0x43010000u, 0x00001c00u, 0x42fe0000u}, {0x3f800000u, 0x00008000u, 0xbf800000u}, {0x43010000u, 0x00001c00u, 0x42fe0000u}, {0x3f800000u, 0x00008000u, 0xbf800000u}, {0x43010000u, 0x00001c00u, 0x42fe0000u}}}},
{{0x7f800000u, 0x00000001u, 0x3f800000u, 0x7f800000u, 0x80400000u, 0xabcd5800u, 0x7f800000u, 0x00000001u, 0x43000000u}, {{{0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0x7f800000u, 0x0000fc00u, 0x7f800000u}, {0x7f800000u, 0x0000fc00u, 0x7f800000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0xffc00000u, 0x0000fe00u, 0xffc00000u}, {0x7f800000u, 0x0000fc00u, 0x7f800000u}, {0x7f800000u, 0x0000fc00u, 0x7f800000u}}}},
{{0x33800000u, 0x7fa00000u, 0x7fc00000u, 0x33800000u, 0x3f800000u, 0xabcd7d00u, 0x33800000u, 0x7fa00000u, 0x7fa00000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00000001u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00000001u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00000001u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00000001u, 0x7fe00000u}}}},
{{0xb8000000u, 0x7fa00000u, 0x477fe000u, 0xb8000000u, 0x3f800000u, 0xabcd0000u, 0xb8000000u, 0x7fa00000u, 0x00000000u}, {{{0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00008200u, 0x7fa00000u}, {0x7fa00000u, 0x00008000u, 0x7fa00000u}, {0x7fa00000u, 0x00008200u, 0x7fa00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00008200u, 0x7fe00000u}, {0x7fe00000u, 0x00008000u, 0x7fe00000u}, {0x7fe00000u, 0x00008200u, 0x7fe00000u}}}},
{{0x33800000u, 0x4f000000u, 0x7fa00000u, 0x33800000u, 0x477ff000u, 0xabcd8000u, 0x33800000u, 0x4f000000u, 0x80000000u}, {{{0x7fa00000u, 0x00008000u, 0x00000000u}, {0x7fa00000u, 0x00001bffu, 0x43000000u}, {0x7fa00000u, 0x00008000u, 0x00000000u}, {0x7fa00000u, 0x00001bffu, 0x43000000u}, {0x7fe00000u, 0x00008000u, 0x00000000u}, {0x7fe00000u, 0x00001bffu, 0x43000000u}, {0x7fe00000u, 0x00008000u, 0x00000000u}, {0x7fe00000u, 0x00001bffu, 0x43000000u}}}},
{{0xb8000000u, 0x7f800000u, 0x00000000u, 0xb8000000u, 0x3f800000u, 0xabcd7bffu, 0xb8000000u, 0x7f800000u, 0x477fe000u}, {{{0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00008200u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00008200u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00008200u, 0xff800000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xff800000u, 0x00008200u, 0xff800000u}}}},
{{0xb8000000u, 0x477ff000u, 0x33800000u, 0xb8000000u, 0xff800000u, 0xabcd0001u, 0xb8000000u, 0x477ff000u, 0x33800000u}, {{{0x00000000u, 0x0000fe00u, 0x00000000u}, {0xbffff000u, 0x00007c00u, 0xbffff000u}, {0x00000000u, 0x0000fe00u, 0x00000000u}, {0xbffff000u, 0x00007c00u, 0xbffff000u}, {0x00000000u, 0x0000fe00u, 0x00000000u}, {0xbffff000u, 0x00007c00u, 0xbffff000u}, {0x00000000u, 0x0000fe00u, 0x00000000u}, {0xbffff000u, 0x00007c00u, 0xbffff000u}}}},
{{0xb8000000u, 0x00000000u, 0x7fc00000u, 0xb8000000u, 0x7f7fffffu, 0xabcd8200u, 0xb8000000u, 0x00000000u, 0xb8000000u}, {{{0x7fc00000u, 0x00008000u, 0x80000000u}, {0x7fc00000u, 0x0000fc00u, 0xb8000000u}, {0x7fc00000u, 0x00008000u, 0x80000000u}, {0x7fc00000u, 0x0000fc00u, 0xb8000000u}, {0x7fc00000u, 0x00008000u, 0x80000000u}, {0x7fc00000u, 0x0000fc00u, 0xb8000000u}, {0x7fc00000u, 0x00008000u, 0x80000000u}, {0x7fc00000u, 0x0000fc00u, 0xb8000000u}}}},
{{0xffc02000u, 0x00000001u, 0x7f800000u, 0xffc02000u, 0x7fa00000u, 0xabcd0000u, 0xffc02000u, 0x00000001u, 0x00000000u}, {{{0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}}}},
{{0xffc02000u, 0x80400000u, 0x3f800000u, 0xffc02000u, 0x47800000u, 0xabcdfe01u, 0xffc02000u, 0x80400000u, 0xffc02000u}, {{{0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}}}},
{{0x7fa00000u, 0x4f000000u, 0x3f800000u, 0x7fa00000u, 0x7fc00000u, 0xabcdfc00u, 0x7fa00000u, 0x4f000000u, 0xff800000u}, {{{0x7fa00000u, 0x00007f00u, 0x7fa00000u}, {0x7fa00000u, 0x00007f00u, 0x7fa00000u}, {0x7fa00000u, 0x00007f00u, 0x7fa00000u}, {0x7fa00000u, 0x00007f00u, 0x7fa00000u}, {0x7fe00000u, 0x00007f00u, 0x7fe00000u}, {0x7fe00000u, 0x00007f00u, 0x7fe00000u}, {0x7fe00000u, 0x00007f00u, 0x7fe00000u}, {0x7fe00000u, 0x00007f00u, 0x7fe00000u}}}},
{{0x7f800000u, 0x00000001u, 0xff800000u, 0x7f800000u, 0x477ff000u, 0xabcd7e00u, 0x7f800000u, 0x00000001u, 0x7fc00000u}, {{{0xffc00000u, 0x00007c00u, 0xffc00000u}, {0xffc00000u, 0x00007c00u, 0xffc00000u}, {0xffc00000u, 0x00007c00u, 0x7fc00000u}, {0xffc00000u, 0x00007c00u, 0x7fc00000u}, {0xffc00000u, 0x00007c00u, 0xffc00000u}, {0xffc00000u, 0x00007c00u, 0xffc00000u}, {0xffc00000u, 0x00007c00u, 0x7fc00000u}, {0xffc00000u, 0x00007c00u, 0x7fc00000u}}}},
{{0xffc02000u, 0x80400000u, 0xb8000000u, 0xffc02000u, 0x00000000u, 0xabcd7c00u, 0xffc02000u, 0x80400000u, 0x7f800000u}, {{{0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}, {0xffc02000u, 0x0000fe01u, 0xffc02000u}}}},
{{0x7f800000u, 0xffc00001u, 0x7fa00000u, 0x7f800000u, 0xff800000u, 0xabcd0000u, 0x7f800000u, 0xffc00001u, 0x00000000u}, {{{0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}, {0xffc00001u, 0x0000fc00u, 0xffc00001u}}}},
{{0x80000000u, 0x7f800000u, 0x43000000u, 0x80000000u, 0x3f800000u, 0xabcd0000u, 0x80000000u, 0x7f800000u, 0x00000000u}, {{{0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}, {0xffc00000u, 0x00008000u, 0xffc00000u}}}},
}};
constexpr std::uint32_t Words = 12;
constexpr std::uint32_t Sentinel = 0xdeadbeefu;
alignas(256) std::array<std::uint32_t, Rows.size() * Words + 4> Data{};
RecompileResult Compile(std::uint32_t mode) {
IrProgram program;
const ShaderStageInputInfo inputs{};
SpirvEmitterState state(program, inputs);
auto& module = state.module;
module.EmitCapability(spv::CapabilityShader);
module.EmitCapability(spv::CapabilityFloat64);
module.EmitCapability(spv::CapabilitySignedZeroInfNanPreserve);
module.EmitExtension("SPV_KHR_float_controls");
module.AddMemoryModel(spv::AddressingModelLogical, spv::MemoryModelGLSL450);
const auto u32 = TypeU32(state);
const auto array = module.DecoratedType(spv::OpTypeRuntimeArray, {{spv::OpDecorate, {spv::DecorationArrayStride, 4u}}}, u32);
const auto structure = module.Type(spv::OpTypeStruct, array);
module.AddAnnotation(spv::OpDecorate, structure, spv::DecorationBlock);
module.AddAnnotation(spv::OpMemberDecorate, structure, 0u, spv::DecorationOffset, 0u);
const auto buffer = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, structure), spv::StorageClassStorageBuffer);
module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationDescriptorSet, 0u);
module.AddAnnotation(spv::OpDecorate, buffer, spv::DecorationBinding, 0u);
const auto invocation = module.DefineGlobalVariable(TypePointer(state, spv::StorageClassInput, TypeU32Vector(state, 3u)), spv::StorageClassInput);
module.AddAnnotation(spv::OpDecorate, invocation, spv::DecorationBuiltIn, spv::BuiltInGlobalInvocationId);
const auto element = module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, u32);
const auto main = module.AllocateId();
const auto voidType = module.Type(spv::OpTypeVoid);
module.EmitEntryPoint(spv::ExecutionModelGLCompute, main, "main", {invocation});
module.AddExecutionMode(main, spv::ExecutionModeLocalSize, static_cast<std::uint32_t>(Rows.size()), 1u, 1u);
for (const auto width : {32u, 64u}) module.AddExecutionMode(main, spv::ExecutionModeSignedZeroInfNanPreserve, width);
module.AddFunction(spv::OpFunction, voidType, main, spv::FunctionControlMaskNone, module.Type(spv::OpTypeFunction, voidType));
module.AddFunction(spv::OpLabel, module.AllocateId());
const auto id = module.AllocateId();
module.AddFunction(spv::OpLoad, TypeU32Vector(state, 3u), id, invocation);
const auto lane = module.AllocateId();
module.AddFunction(spv::OpCompositeExtract, u32, lane, id, 0u);
const auto rowBase = Binary(state, spv::OpIMul, u32, lane, ConstantU32(state, Words));
const auto address = [&](std::uint32_t offset) {
const auto pointer = module.AllocateId();
module.AddFunction(spv::OpAccessChain, element, pointer, buffer, ConstantU32(state, 0u), Binary(state, spv::OpIAdd, u32, rowBase, ConstantU32(state, offset)));
return pointer;
};
const auto load = [&](std::uint32_t offset) {
const auto word = module.AllocateId();
module.AddFunction(spv::OpLoad, u32, word, address(offset));
return Unary(state, spv::OpBitcast, TypeF32(state), word);
};
IrProgram constants;
IrBuilder builder(constants);
builder.SetInsertionPoint(constants.CreateBlock());
const auto* flags = &builder.Constant(mode);
for (std::uint32_t op = 0; op < 3u; ++op) {
const auto delta = load(op * 3u);
const auto coordinate = load(op * 3u + 1u);
const auto base = load(op * 3u + 2u);
const auto value = op == 1u ? EmitFPInterpolateF16(state, delta, coordinate, base, flags) : EmitFPInterpolateF32(state, delta, coordinate, base, flags);
const auto result = op == 1u ? EmitConvertF16F32(state, value) : Unary(state, spv::OpBitcast, u32, value);
module.AddFunction(spv::OpStore, address(9u + op), result);
}
module.AddFunction(spv::OpReturn);
module.AddFunction(spv::OpFunctionEnd);
RecompileResult result{};
result.spirv = module.Finalize();
const auto base = reinterpret_cast<std::uintptr_t>(Data.data());
result.bindings.push_back({DescriptorKind::StorageBuffer, DescriptorRole::GuestBuffers, 0u, 0u, 1u,
{static_cast<std::uint32_t>(base), static_cast<std::uint32_t>((base >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(Data)), 0x31016facu}, false});
return result;
}
void Run(AgcDriver::VulkanDevice& device, std::uint32_t mode) {
Data.fill(Sentinel);
for (std::uint32_t row = 0; row < Rows.size(); ++row) std::copy(Rows[row].input.begin(), Rows[row].input.end(), Data.begin() + row * Words);
device.Dispatch(Compile(ModeFlags[mode]), 1u, 1u, 1u);
device.WaitIdle();
for (std::uint32_t row = 0; row < Rows.size(); ++row) {
for (std::uint32_t input = 0; input < 9u; ++input) Require(Data[row * Words + input] == Rows[row].input[input], "interpolation modes changed an input");
for (std::uint32_t op = 0; op < 3u; ++op) {
const auto actual = Data[row * Words + 9u + op];
const auto expected = Rows[row].expected[mode][op];
char message[200];
std::snprintf(message, sizeof(message), "%s, %s, row %u: got 0x%08x, expected 0x%08x", OpNames[op], ModeNames[mode], row, actual, expected);
Require(actual == expected, message);
}
}
for (std::size_t index = Rows.size() * Words; index < Data.size(); ++index) Require(Data[index] == Sentinel, "interpolation modes wrote beyond the rows");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto target = device->Target();
if (std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityFloat64) == target.supportedCapabilities.end()) {
std::puts("skipped, interpolation modes require Float64");
return VulkanTestSkipped;
}
for (std::uint32_t mode = 0; mode < ModeFlags.size(); ++mode) Run(*device, mode);
std::puts("interpolation modes passed: 32 hardware rows, 3 ops, 8 float modes");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -262,6 +262,8 @@ enum class IrOpcode : std::uint16_t {
FPSqrt64,
FPTrigPreop64,
FPDot2F32F16,
FPInterpolateF32,
FPInterpolateF16,
ConvertF32F64,
ConvertF64F32,
ConvertF64S32,
@ -457,6 +459,7 @@ enum class IrOpcode : std::uint16_t {
ImageAtomicCmpSwap64,
GetAttribute,
GetInterpolationParameter,
GetInterpolationParameterF16,
SetAttribute,
ControlNop,
Waitcnt,

View File

@ -303,6 +303,8 @@ constexpr std::array<OpcodeMeta, static_cast<std::size_t>(IrOpcode::Count)> Meta
makeMeta("FPSqrt64", U64, U64),
makeMeta("FPTrigPreop64", U64, U64, U32),
makeMeta("FPDot2F32F16", U32, U32, U32, U32),
makeMeta("FPInterpolateF32", F32, F32, F32, F32, U32),
makeMeta("FPInterpolateF16", F32, F32, F32, F32, U32),
makeMeta("ConvertF32F64", F32, U64),
makeMeta("ConvertF64F32", U64, F32),
makeMeta("ConvertF64S32", U64, U32),
@ -498,6 +500,7 @@ constexpr std::array<OpcodeMeta, static_cast<std::size_t>(IrOpcode::Count)> Meta
makeMeta("ImageAtomicCmpSwap64", U64, ImageResource, ImageAddress, U64, U64, U1),
makeMeta("GetAttribute", U32, U32, U32),
makeMeta("GetInterpolationParameter", U32, U32, U32, U32),
makeMeta("GetInterpolationParameterF16", F32, U32, U32, U32, U32),
makeMeta("SetAttribute", Void, U32x4, U1),
makeMeta("ControlNop", Void),
makeMeta("Waitcnt", Void),

View File

@ -201,6 +201,26 @@ struct ShaderPixelInputInfo {
return one ? 0x3f800000u : 0u;
}
[[nodiscard]] bool InputIsFp16(std::uint32_t input) const {
return input < inputNum && input < 32u && (interpolatorSettings[input] & 0x80000u) != 0u;
}
[[nodiscard]] bool InputHalfIsDefault(std::uint32_t input, bool high) const {
if (!high) {
return InputIsDefault(input);
}
return input < inputNum && input < 32u && (interpolatorSettings[input] & 0x100000u) != 0u;
}
[[nodiscard]] std::uint32_t InputHalfDefaultBits(std::uint32_t input, std::uint32_t component, bool high) const {
if (!high) {
return InputDefaultBits(input, component);
}
const auto value = input < 32u ? (interpolatorSettings[input] >> 21u) & 0x3u : 0u;
const bool one = component == 3u ? (value & 0x1u) != 0u : (value & 0x2u) != 0u;
return one ? 0x3f800000u : 0u;
}
[[nodiscard]] bool InputIsLinear(std::uint32_t input, std::uint32_t linearInputs, std::uint32_t perspectiveInputs) const {
const auto bit = input < 32u ? 1u << input : 0u;
if ((linearInputs & perspectiveInputs & bit) != 0u) {

View File

@ -106,6 +106,20 @@ void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inpu
}
break;
}
case IrOpcode::GetInterpolationParameterF16: {
const IrValue* input = inst->Argument(0)->Resolve();
const IrValue* component = inst->Argument(1)->Resolve();
const IrValue* mode = inst->Argument(2)->Resolve();
const IrValue* high = inst->Argument(3)->Resolve();
if (program.Resources().stage != IrShaderStage::Pixel || !input->HasImmediate() || input->Type() != IrType::U32 || !component->HasImmediate() || component->Type() != IrType::U32 ||
!mode->HasImmediate() || mode->Type() != IrType::U32 || !high->HasImmediate() || high->Type() != IrType::U32) {
return Fail("16-bit interpolation parameter reference is invalid");
}
if (input->ImmediateU32() >= inputInfo.pixel->inputNum || component->ImmediateU32() >= 4u || mode->ImmediateU32() >= 3u || high->ImmediateU32() >= 2u) {
return Fail("16-bit interpolation parameter reference is out of range");
}
break;
}
case IrOpcode::GetBuiltin: {
const IrValue* kindValue = inst->Argument(0)->Resolve();
const IrValue* componentValue = inst->Argument(1)->Resolve();
@ -219,6 +233,7 @@ void CollectPixelInputs(const IrProgram& program, const ShaderPixelInputInfo* pi
std::array<bool, 32> read {};
std::array<bool, 32> perVertex {};
std::array<bool, 32> interpolated {};
std::array<bool, 32> halfRead {};
for (const auto& block : program.Blocks()) {
for (const IrValue* inst : block->Instructions()) {
if (inst->Opcode() == IrOpcode::GetAttribute) {
@ -233,6 +248,14 @@ void CollectPixelInputs(const IrProgram& program, const ShaderPixelInputInfo* pi
const auto mode = inst->Argument(2)->Resolve()->ImmediateU32();
read[input] = true;
perVertex[input] = perVertex[input] || mode < 2u || !IsPixelParameterFlat(*pixel, input);
} else if (inst->Opcode() == IrOpcode::GetInterpolationParameterF16) {
const auto input = inst->Argument(0)->Resolve()->ImmediateU32();
const bool high = inst->Argument(3)->Resolve()->ImmediateU32() != 0u;
if (!pixel->InputHalfIsDefault(input, high)) {
read[input] = true;
perVertex[input] = true;
halfRead[input] = true;
}
}
}
}
@ -248,7 +271,7 @@ void CollectPixelInputs(const IrProgram& program, const ShaderPixelInputInfo* pi
slotInterpolation.fill(unassigned);
std::array<bool, 32> slotPerVertex {};
for (std::uint32_t input = 0; input < pixel->inputNum; input++) {
if (!read[input] || pixel->InputIsDefault(input)) {
if (!read[input] || (pixel->InputIsDefault(input) && !halfRead[input])) {
continue;
}
const auto slot = pixel->InputSlot(input);
@ -262,7 +285,7 @@ void CollectPixelInputs(const IrProgram& program, const ShaderPixelInputInfo* pi
bool smooth = false;
bool noPerspective = false;
for (std::uint32_t input = 0; input < pixel->inputNum; input++) {
if (!read[input] || pixel->InputIsDefault(input)) {
if (!read[input] || (pixel->InputIsDefault(input) && !halfRead[input])) {
continue;
}
const bool vertexInput = slotPerVertex[pixel->InputSlot(input)];

View File

@ -1043,6 +1043,9 @@ enum class RdnaOpcode : std::uint16_t {
VInterpP1F32,
VInterpP2F32,
VInterpMovF32,
VInterpP1llF16,
VInterpP1lvF16,
VInterpP2F16,
SWaitcntDepctr,
SSendmsg,
SGetregB32,

View File

@ -623,6 +623,9 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) {
case RdnaOpcode::VInterpP1F32:
case RdnaOpcode::VInterpP2F32:
case RdnaOpcode::VInterpMovF32:
case RdnaOpcode::VInterpP1llF16:
case RdnaOpcode::VInterpP1lvF16:
case RdnaOpcode::VInterpP2F16:
return true;
default:
return false;

View File

@ -493,6 +493,7 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = {
{0x357u, RdnaOpcode::VMed3F16},
{0x358u, RdnaOpcode::VMed3I16},
{0x359u, RdnaOpcode::VMed3U16},
{0x35au, RdnaOpcode::VInterpP2F16},
{0x35fu, RdnaOpcode::VDivFixupF16},
{0x15au, RdnaOpcode::VSadU8},
{0x15bu, RdnaOpcode::VSadHiU8},
@ -534,6 +535,8 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = {
{0x311u, RdnaOpcode::VPackB32F16},
{0x314u, RdnaOpcode::VLshlrevB16},
{0x319u, RdnaOpcode::VSubrevI32},
{0x342u, RdnaOpcode::VInterpP1llF16},
{0x343u, RdnaOpcode::VInterpP1lvF16},
{0x344u, RdnaOpcode::VPermB32},
{0x14du, RdnaOpcode::VLerpU8},
{0x345u, RdnaOpcode::VXadU32},
@ -2073,6 +2076,43 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut,
}
}
bool isVop3F16InterpolationOpcode(RdnaOpcode opcode) {
return opcode == RdnaOpcode::VInterpP1llF16 || opcode == RdnaOpcode::VInterpP1lvF16 || opcode == RdnaOpcode::VInterpP2F16;
}
void decodeVop3F16Interpolation(RdnaInstruction& instruction, std::uint32_t vdst, std::uint32_t src0, std::uint32_t src1, std::uint32_t src2, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) {
const bool hasSource2 = instruction.op != RdnaOpcode::VInterpP1llF16;
const bool f16Result = instruction.op == RdnaOpcode::VInterpP2F16;
if ((abs & 0x1u) != 0u || (neg & 0x1u) != 0u || opSel != 0u || (f16Result && omod != 0u) || (!hasSource2 && ((abs | neg) & 0x4u) != 0u)) {
throw std::invalid_argument("VOP3 interpolation modifiers are not implemented");
}
if (src1 < 256u || (hasSource2 && src2 < 256u)) {
throw std::invalid_argument("VOP3 interpolation source is not a vector register");
}
const bool high = ((src0 >> 8u) & 0x1u) != 0u;
instruction.destination = DecodeRdnaVectorGpr(vdst);
instruction.destination.clamp = clamp != 0u;
instruction.destination.omod = omod;
instruction.source0 = DecodeRdnaVectorGpr(src1 - 256u);
instruction.source0.absolute = (abs & 0x2u) != 0u;
instruction.source0.negate = (neg & 0x2u) != 0u;
instruction.source1.kind = RdnaOperandKind::IntegerInlineConstant;
instruction.source1.value = src0 & 0x3fu;
instruction.source1.signedVal = static_cast<std::int32_t>(instruction.source1.value);
instruction.source1.opSel = high;
instruction.source2.kind = RdnaOperandKind::IntegerInlineConstant;
instruction.source2.value = (src0 >> 6u) & 0x3u;
instruction.source2.signedVal = static_cast<std::int32_t>(instruction.source2.value);
instruction.sourceCount = 3;
if (hasSource2) {
instruction.source3 = DecodeRdnaVectorGpr(src2 - 256u);
instruction.source3.absolute = (abs & 0x4u) != 0u;
instruction.source3.negate = (neg & 0x4u) != 0u;
instruction.source3.opSel = high && !f16Result;
instruction.sourceCount = 4;
}
}
void applyNativeVop3SourceModifiers(RdnaInstruction& instruction, std::uint32_t abs, std::uint32_t neg) {
RdnaOperand* sources[] = {&instruction.source0, &instruction.source1, &instruction.source2};
for (std::uint32_t i = 0; i < instruction.sourceCount && i < 3u; ++i) {
@ -2233,6 +2273,11 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span<const std
instruction.op = lookupVop3Opcode(opcode);
SetRdnaRawWords(instruction, code, wordIndex, 2);
if (isVop3F16InterpolationOpcode(instruction.op)) {
decodeVop3F16Interpolation(instruction, vdst, src0, src1, src2, abs, opSel, clamp, omod, neg);
return instruction;
}
const bool carryInOut = (instruction.op == RdnaOpcode::VAddcU32 && opcode == 0x128u) ||
(instruction.op == RdnaOpcode::VSubCoCiU32 && opcode == 0x129u) ||
(instruction.op == RdnaOpcode::VSubrevCoCiU32 && opcode == 0x12au);

View File

@ -164,6 +164,10 @@ struct ShaderFloatMode {
bool operator==(const ShaderFloatMode&) const = default;
};
inline constexpr std::uint32_t InterpolationQuiet = 1u;
inline constexpr std::uint32_t InterpolationFlush32 = 2u;
inline constexpr std::uint32_t InterpolationFlush16 = 4u;
struct GuestContext {
std::uint32_t waveSize;
std::uint32_t userDataBaseRegister;

View File

@ -78,6 +78,7 @@ std::uint32_t EmitWriteLane(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitPermlane16U32(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetInterpolationParameter(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetInterpolationParameterF16(SpirvValueEmitContext& ctx, const IrValue& inst);
void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetShaderBase(SpirvValueEmitContext& ctx);
std::uint32_t EmitShaderClock(SpirvValueEmitContext& ctx, const IrValue& inst);
@ -196,6 +197,8 @@ std::uint32_t EmitFPRsq64(SpirvEmitterState& state, std::uint32_t arg0);
std::uint32_t EmitFPSqrt64(SpirvEmitterState& state, std::uint32_t arg0);
std::uint32_t EmitFPTrigPreop64(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1);
std::uint32_t EmitFPDot2F32F16(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2);
std::uint32_t EmitFPInterpolateF32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, const IrValue* mode);
std::uint32_t EmitFPInterpolateF16(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, const IrValue* mode);
std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0);
std::uint32_t EmitConvertF64F32(SpirvEmitterState& state, std::uint32_t arg0);
std::uint32_t EmitConvertF64S32(SpirvEmitterState& state, std::uint32_t arg0);

View File

@ -197,6 +197,98 @@ std::uint32_t AddRoundToOdd(SpirvEmitterState& state, std::uint32_t lhs, std::ui
return ToF64(state, Join(state, SelectBits(state, inexact, odd, rounded)));
}
std::uint32_t FromF32Exact(SpirvEmitterState& state, std::uint32_t value) {
const auto u32 = TypeU32(state);
const auto bits = Unary(state, spv::OpBitcast, u32, value);
const auto sign = Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x80000000u));
const auto magnitude = Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x7fffffffu));
const auto subnormal = Binary(state, spv::OpULessThan, TypeBool(state), magnitude, ConstantU32(state, 0x00800000u));
const auto scaled = Exact(state, spv::OpFMul, Unary(state, spv::OpConvertUToF, TypeF64(state), magnitude), ConstantF64(state, 0x36a0000000000000ull));
const F64Bits normal{Binary(state, spv::OpShiftLeftLogical, u32, magnitude, ConstantU32(state, 29u)), Binary(state, spv::OpIAdd, u32, Binary(state, spv::OpShiftRightLogical, u32, magnitude, ConstantU32(state, 3u)), ConstantU32(state, 896u << 20u))};
const F64Bits special{normal.low, Binary(state, spv::OpBitwiseOr, u32, Binary(state, spv::OpShiftRightLogical, u32, Binary(state, spv::OpBitwiseAnd, u32, magnitude, ConstantU32(state, 0x007fffffu)), ConstantU32(state, 3u)), ConstantU32(state, 0x7ff00000u))};
const auto nonFinite = Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), magnitude, ConstantU32(state, 0x7f800000u));
const auto unsignedBits = SelectBits(state, nonFinite, special, Split(state, FromF64(state, Select(state, TypeF64(state), subnormal, scaled, ToF64(state, Join(state, normal))))));
return ToF64(state, Join(state, {unsignedBits.low, Binary(state, spv::OpBitwiseOr, u32, unsignedBits.high, sign)}));
}
std::uint32_t RoundToPrecision(SpirvEmitterState& state, std::uint32_t value, std::uint32_t fractionBits, std::uint32_t smallestStep) {
const auto u32 = TypeU32(state);
const auto bits = Split(state, FromF64(state, value));
const auto exponent = Unary(state, spv::OpBitcast, TypeI32(state), ExponentField(state, bits.high));
const auto step = Glsl(state, TypeI32(state), GLSLstd450SMax, Binary(state, spv::OpISub, TypeI32(state), exponent, ConstantI32(state, static_cast<std::int32_t>(fractionBits))), ConstantI32(state, static_cast<std::int32_t>(smallestStep)));
const auto sign = Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x80000000u));
const auto magicExponent = Binary(state, spv::OpShiftLeftLogical, u32, Unary(state, spv::OpBitcast, u32, Binary(state, spv::OpIAdd, TypeI32(state), step, ConstantI32(state, 52))), ConstantU32(state, 20u));
const auto magic = ToF64(state, Join(state, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, u32, sign, magicExponent)}));
const auto rounded = Split(state, FromF64(state, Exact(state, spv::OpFSub, Exact(state, spv::OpFAdd, value, magic), magic)));
return ToF64(state, Join(state, {rounded.low, Binary(state, spv::OpBitwiseOr, u32, Binary(state, spv::OpBitwiseAnd, u32, rounded.high, ConstantU32(state, 0x7fffffffu)), sign)}));
}
std::uint32_t F32BitsOfRounded(SpirvEmitterState& state, std::uint32_t value) {
const auto u32 = TypeU32(state);
const auto bits = Split(state, FromF64(state, value));
const auto sign = Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x80000000u));
const auto exponent = ExponentField(state, bits.high);
const auto fraction = Binary(state, spv::OpBitwiseOr, u32, Binary(state, spv::OpShiftLeftLogical, u32, Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x000fffffu)), ConstantU32(state, 3u)), Binary(state, spv::OpShiftRightLogical, u32, bits.low, ConstantU32(state, 29u)));
const auto normal = Binary(state, spv::OpBitwiseOr, u32, Binary(state, spv::OpShiftLeftLogical, u32, Binary(state, spv::OpISub, u32, exponent, ConstantU32(state, 896u)), ConstantU32(state, 23u)), fraction);
const auto magnitude = ToF64(state, Join(state, {bits.low, Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x7fffffffu))}));
const auto subnormal = Unary(state, spv::OpConvertFToU, u32, Exact(state, spv::OpFMul, magnitude, ConstantF64(state, 0x4940000000000000ull)));
auto result = Select(state, u32, Binary(state, spv::OpULessThan, TypeBool(state), exponent, ConstantU32(state, 897u)), subnormal, normal);
result = Select(state, u32, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), exponent, ConstantU32(state, 1151u)), ConstantU32(state, 0x7f800000u), result);
return Binary(state, spv::OpBitwiseOr, u32, result, sign);
}
std::uint32_t NonFinite32(SpirvEmitterState& state, std::uint32_t bits) {
const auto exponent = Binary(state, spv::OpBitwiseAnd, TypeU32(state), bits, ConstantU32(state, 0x7f800000u));
return Binary(state, spv::OpIEqual, TypeBool(state), exponent, ConstantU32(state, 0x7f800000u));
}
std::uint32_t Nan32(SpirvEmitterState& state, std::uint32_t bits) {
return Binary(state, spv::OpUGreaterThan, TypeBool(state), Binary(state, spv::OpBitwiseAnd, TypeU32(state), bits, ConstantU32(state, 0x7fffffffu)), ConstantU32(state, 0x7f800000u));
}
std::uint32_t FlushDenormal32(SpirvEmitterState& state, std::uint32_t value, bool flush, std::uint32_t smallestNormal) {
if (!flush) return value;
const auto u32 = TypeU32(state);
const auto bits = Unary(state, spv::OpBitcast, u32, value);
const auto magnitude = Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x7fffffffu));
const auto denormal = Binary(state, spv::OpLogicalAnd, TypeBool(state), Binary(state, spv::OpINotEqual, TypeBool(state), magnitude, ConstantU32(state, 0u)), Binary(state, spv::OpULessThan, TypeBool(state), magnitude, ConstantU32(state, smallestNormal)));
return Unary(state, spv::OpBitcast, TypeF32(state), Select(state, u32, denormal, Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x80000000u)), bits));
}
std::uint32_t Interpolate(SpirvEmitterState& state, std::uint32_t delta, std::uint32_t coordinate, std::uint32_t base, bool halfResult, std::uint32_t mode) {
const auto u32 = TypeU32(state);
const bool quietNans = halfResult || (mode & InterpolationQuiet) != 0u;
const bool flush32 = (mode & InterpolationFlush32) != 0u;
const bool flush16 = (mode & InterpolationFlush16) != 0u;
delta = FlushDenormal32(state, delta, flush16, 0x38800000u);
coordinate = FlushDenormal32(state, coordinate, flush32, 0x00800000u);
base = halfResult ? FlushDenormal32(state, base, flush32, 0x00800000u) : FlushDenormal32(state, base, flush16, 0x38800000u);
const auto product = Exact(state, spv::OpFMul, FromF32Exact(state, delta), FromF32Exact(state, coordinate));
const auto sum = AddRoundToOdd(state, product, FromF32Exact(state, base));
auto rounded = halfResult ? RoundToPrecision(state, sum, 10u, 999u) : RoundToPrecision(state, sum, 23u, 874u);
if (halfResult) {
const auto bits = Split(state, FromF64(state, rounded));
const auto sign = Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x80000000u));
const auto overflow = Binary(state, spv::OpUGreaterThan, TypeBool(state), Binary(state, spv::OpBitwiseAnd, u32, bits.high, ConstantU32(state, 0x7fffffffu)), ConstantU32(state, 0x40f00000u));
rounded = ToF64(state, Join(state, SelectBits(state, overflow, {ConstantU32(state, 0u), Binary(state, spv::OpBitwiseOr, u32, sign, ConstantU32(state, 0x40f00000u))}, bits)));
}
const auto finite = F32BitsOfRounded(state, rounded);
const auto inputs = std::array{Unary(state, spv::OpBitcast, u32, delta), Unary(state, spv::OpBitcast, u32, coordinate), Unary(state, spv::OpBitcast, u32, base)};
const auto unbounded = Split(state, FromF64(state, Exact(state, spv::OpFAdd, product, FromF32Exact(state, base))));
auto special = Select(state, u32, Classify(state, Join(state, unbounded)).nan, ConstantU32(state, 0xffc00000u), Binary(state, spv::OpBitwiseOr, u32, Binary(state, spv::OpBitwiseAnd, u32, unbounded.high, ConstantU32(state, 0x80000000u)), ConstantU32(state, 0x7f800000u)));
auto nonFinite = NonFinite32(state, inputs[0]);
for (const auto input : {inputs[1], inputs[2]}) nonFinite = Binary(state, spv::OpLogicalOr, TypeBool(state), nonFinite, NonFinite32(state, input));
const auto zero = [&](std::uint32_t bits) { return Binary(state, spv::OpIEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x7fffffffu)), ConstantU32(state, 0u)); };
const auto infinite = [&](std::uint32_t bits) { return Binary(state, spv::OpIEqual, TypeBool(state), Binary(state, spv::OpBitwiseAnd, u32, bits, ConstantU32(state, 0x7fffffffu)), ConstantU32(state, 0x7f800000u)); };
const auto invalidProduct = Binary(state, spv::OpLogicalOr, TypeBool(state), Binary(state, spv::OpLogicalAnd, TypeBool(state), infinite(inputs[0]), zero(inputs[1])), Binary(state, spv::OpLogicalAnd, TypeBool(state), zero(inputs[0]), infinite(inputs[1])));
const auto quiet = [&](std::uint32_t bits) { return quietNans ? Binary(state, spv::OpBitwiseOr, u32, bits, ConstantU32(state, 0x00400000u)) : bits; };
special = Select(state, u32, Binary(state, spv::OpLogicalAnd, TypeBool(state), Nan32(state, inputs[2]), Unary(state, spv::OpLogicalNot, TypeBool(state), invalidProduct)), quiet(inputs[2]), special);
special = Select(state, u32, Nan32(state, inputs[1]), quiet(inputs[1]), special);
special = Select(state, u32, Nan32(state, inputs[0]), quiet(inputs[0]), special);
const auto result = Unary(state, spv::OpBitcast, TypeF32(state), Select(state, u32, nonFinite, special, finite));
return halfResult ? FlushDenormal32(state, result, flush16, 0x38800000u) : FlushDenormal32(state, result, flush32, 0x00800000u);
}
std::uint32_t FusedMultiplyAdd(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, std::uint32_t scale) {
const auto a = Classify(state, arg0);
const auto b = Classify(state, arg1);
@ -532,6 +624,14 @@ std::uint32_t EmitFPDot2F32F16(SpirvEmitterState& state, std::uint32_t arg0, std
return pick(aLow.nan, quietHalf(aLow), result);
}
std::uint32_t EmitFPInterpolateF32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, const IrValue* mode) {
return Interpolate(state, arg0, arg1, arg2, false, mode->ImmediateU32());
}
std::uint32_t EmitFPInterpolateF16(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2, const IrValue* mode) {
return Interpolate(state, arg0, arg1, arg2, true, mode->ImmediateU32());
}
std::uint32_t EmitConvertF32F64(SpirvEmitterState& state, std::uint32_t arg0) {
const auto converted = Unary(state, spv::OpBitcast, TypeU32(state), Unary(state, spv::OpFConvert, TypeF32(state), ToF64(state, arg0)));
const auto cls = Classify(state, arg0);

View File

@ -37,6 +37,7 @@ bool LaneSource(const IrProgram& program, const IrValue& value) {
case IrOpcode::LaneId:
case IrOpcode::GetAttribute:
case IrOpcode::GetInterpolationParameter:
case IrOpcode::GetInterpolationParameterF16:
case IrOpcode::GetTessellationAttribute:
case IrOpcode::DppMoveU32:
case IrOpcode::DppUpdateU32:

View File

@ -476,6 +476,8 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) {
case IrOpcode::FPSqrt64: return Invoke(EmitFPSqrt64, ctx, inst);
case IrOpcode::FPTrigPreop64: return Invoke(EmitFPTrigPreop64, ctx, inst);
case IrOpcode::FPDot2F32F16: return Invoke(EmitFPDot2F32F16, ctx, inst);
case IrOpcode::FPInterpolateF32: return Invoke(EmitFPInterpolateF32, ctx, inst);
case IrOpcode::FPInterpolateF16: return Invoke(EmitFPInterpolateF16, ctx, inst);
case IrOpcode::ConvertF32F64: return Invoke(EmitConvertF32F64, ctx, inst);
case IrOpcode::ConvertF64F32: return Invoke(EmitConvertF64F32, ctx, inst);
case IrOpcode::ConvertF64S32: return Invoke(EmitConvertF64S32, ctx, inst);
@ -672,6 +674,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) {
case IrOpcode::ImageAtomicCmpSwap64: return Invoke(EmitImageAtomicCmpSwap64, ctx, inst);
case IrOpcode::GetAttribute: return Invoke(EmitGetAttribute, ctx, inst);
case IrOpcode::GetInterpolationParameter: return Invoke(EmitGetInterpolationParameter, ctx, inst);
case IrOpcode::GetInterpolationParameterF16: return Invoke(EmitGetInterpolationParameterF16, ctx, inst);
case IrOpcode::SetAttribute: return Invoke(EmitSetAttribute, ctx, inst);
case IrOpcode::ControlNop: return Invoke(EmitControlNop, ctx, inst);
case IrOpcode::Waitcnt: return Invoke(EmitWaitcnt, ctx, inst);

View File

@ -28,7 +28,7 @@ namespace ShaderRecompiler
if (input >= PixelParameterLimit || input >= info.inputNum) {
FailEmit("pixel interpolator index is out of range");
}
if (info.InputIsDefault(input)) {
if (info.InputIsDefault(input) && (!info.InputIsFp16(input) || info.InputHalfIsDefault(input, true))) {
FailEmit("pixel input " + std::to_string(input) + " reads a default value, not a parameter");
}
return info.InputSlot(input);

View File

@ -200,6 +200,38 @@ std::uint32_t EmitInterpolationParameterValue(SpirvEmitterState& state, std::uin
return bits;
}
std::uint32_t EmitInterpolationParameterF16Value(SpirvEmitterState& state, std::uint32_t attr, std::uint32_t chan, std::uint32_t mode, bool high) {
const auto& pixel = *state.inputInfo.pixel;
if (pixel.InputHalfIsDefault(attr, high)) {
return ConstantF32(state, mode == 2u ? pixel.InputHalfDefaultBits(attr, chan & 3u, high) : 0u);
}
const auto* input = SpirvInputBindingForParameter(state, attr);
if (input == nullptr || !input->perVertex) {
throw std::runtime_error("16-bit interpolation parameter is not read per vertex");
}
const auto loadVertex = [&](std::uint32_t vertex) {
const auto pointer = state.module.AllocateId();
const auto value = state.module.AllocateId();
auto bits = state.module.AllocateId();
state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer, input->variableId, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
state.module.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
if (high) {
const auto shifted = state.module.AllocateId();
state.module.AddFunction(spv::OpShiftRightLogical, TypeU32(state), shifted, bits, ConstantU32(state, 16u));
bits = shifted;
}
return EmitF16BitsToF32(state, bits);
};
const auto value = loadVertex((mode + 1u) % 3u);
if (mode == 2u) {
return value;
}
const auto delta = state.module.AllocateId();
state.module.AddFunction(spv::OpFSub, TypeF32(state), delta, value, loadVertex(0u));
return EmitF16BitsToF32(state, EmitConvertF16F32(state, delta));
}
std::uint32_t MrtOutputMode(const SpirvEmitterState& state, const ExportInfo& exp) {
if (state.program.Resources().stage != IrShaderStage::Pixel || exp.kind != ExportTargetKind::Mrt) {
return 0u;
@ -1085,6 +1117,10 @@ std::uint32_t EmitGetInterpolationParameter(SpirvValueEmitContext& ctx, const Ir
return EmitInterpolationParameterValue(ctx.state, inst.Argument(0)->ImmediateU32(), inst.Argument(1)->ImmediateU32(), inst.Argument(2)->ImmediateU32());
}
std::uint32_t EmitGetInterpolationParameterF16(SpirvValueEmitContext& ctx, const IrValue& inst) {
return EmitInterpolationParameterF16Value(ctx.state, inst.Argument(0)->ImmediateU32(), inst.Argument(1)->ImmediateU32(), inst.Argument(2)->ImmediateU32(), inst.Argument(3)->ImmediateU32() != 0u);
}
void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) {
auto& state = ctx.state;
const auto& exp = ctx.Export(inst);

View File

@ -299,6 +299,10 @@ private:
void vInterpP1F32(const RdnaInstruction& inst);
void vInterpP2F32(const RdnaInstruction& inst);
void vInterpMovF32(const RdnaInstruction& inst);
std::uint32_t interpolationModeF16(const RdnaInstruction& inst) const;
IrF32 interpolationParameterF16(const RdnaInstruction& inst, std::uint32_t mode);
void vInterpP1F16(const RdnaInstruction& inst);
void vInterpP2F16(const RdnaInstruction& inst);
void eXP(const RdnaInstruction& inst);
bool emitScalar(const RdnaInstruction& inst);
bool emitVector(const RdnaInstruction& inst);

View File

@ -93,6 +93,40 @@ void TranslationContext::vInterpMovF32(const RdnaInstruction& inst) {
writeOperand(inst.destination, &value);
}
IrF32 TranslationContext::interpolationParameterF16(const RdnaInstruction& inst, std::uint32_t mode) {
if (!fragmentShaderBarycentricEnabled) {
throw std::runtime_error("16-bit interpolation of pixel input " + std::to_string(inst.source1.value) + " requires fragmentShaderBarycentric");
}
if (pixelInput == nullptr || !pixelInput->InputIsFp16(inst.source1.value)) {
throw std::runtime_error("pixel input " + std::to_string(inst.source1.value) + " is read with 16-bit interpolation without FP16_INTERP_MODE");
}
return IrF32(ir.Emit(IrOpcode::GetInterpolationParameterF16, IrType::F32, {&ir.Constant(inst.source1.value), &ir.Constant(inst.source2.value), &ir.Constant(mode), &ir.Constant(inst.source1.opSel ? 1u : 0u)}));
}
std::uint32_t TranslationContext::interpolationModeF16(const RdnaInstruction& inst) const {
if (!floatMode.has_value()) return 0u;
const auto mode = floatMode->floatMode;
const auto denorm32 = (mode >> 4u) & 3u;
const auto denorm16 = (mode >> 6u) & 3u;
if ((mode & 0xfu) != 0u || (denorm32 != 0u && denorm32 != 3u) || (denorm16 != 0u && denorm16 != 3u)) {
throw std::runtime_error("16-bit interpolation at pc " + std::to_string(inst.programCounter) + " in FLOAT_MODE " + std::to_string(mode) + " is not measured");
}
return (floatMode->ieeeMode ? InterpolationQuiet : 0u) | (denorm32 == 0u ? InterpolationFlush32 : 0u) | (denorm16 == 0u ? InterpolationFlush16 : 0u);
}
void TranslationContext::vInterpP1F16(const RdnaInstruction& inst) {
const auto mode = interpolationModeF16(inst);
const IrF32 delta = interpolationParameterF16(inst, 0u);
const IrF32 origin = inst.op == RdnaOpcode::VInterpP1lvF16 ? readF16AsF32(inst.source3) : interpolationParameterF16(inst, 2u);
writeOperand(inst.destination, &ir.Emit(IrOpcode::FPInterpolateF32, IrType::F32, {&delta.Value(), readOperand(inst.source0, IrType::F32), &origin.Value(), &ir.Constant(mode)}));
}
void TranslationContext::vInterpP2F16(const RdnaInstruction& inst) {
const auto mode = interpolationModeF16(inst);
const IrF32 delta = interpolationParameterF16(inst, 1u);
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {&delta.Value(), readOperand(inst.source0, IrType::F32), readOperand(inst.source3, IrType::F32), &ir.Constant(mode)})));
}
void TranslationContext::eXP(const RdnaInstruction& inst) {
std::uint32_t index = 0u;
if (exportTargetKindFromTarget(inst.exportTarget, index) == ExportTargetKind::Unknown) {
@ -109,7 +143,8 @@ void TranslationContext::eXP(const RdnaInstruction& inst) {
}
bool TranslationContext::emitInterpolation(const RdnaInstruction& inst) {
if ((inst.op == RdnaOpcode::VInterpP1F32 || inst.op == RdnaOpcode::VInterpP2F32) && pixelInput != nullptr && pixelInput->InputIsCustom(inst.source1.value)) {
const bool interpolates = inst.op == RdnaOpcode::VInterpP1F32 || inst.op == RdnaOpcode::VInterpP2F32 || inst.op == RdnaOpcode::VInterpP1llF16 || inst.op == RdnaOpcode::VInterpP1lvF16 || inst.op == RdnaOpcode::VInterpP2F16;
if (interpolates && pixelInput != nullptr && pixelInput->InputIsCustom(inst.source1.value)) {
throw std::runtime_error("pixel input " + std::to_string(inst.source1.value) + " passes its vertices through unchanged but is read with v_interp_p1/p2");
}
switch (inst.op) {
@ -122,6 +157,13 @@ bool TranslationContext::emitInterpolation(const RdnaInstruction& inst) {
case RdnaOpcode::VInterpMovF32:
vInterpMovF32(inst);
return true;
case RdnaOpcode::VInterpP1llF16:
case RdnaOpcode::VInterpP1lvF16:
vInterpP1F16(inst);
return true;
case RdnaOpcode::VInterpP2F16:
vInterpP2F16(inst);
return true;
default:
return false;
}

View File

@ -38,7 +38,7 @@ void TranslationContext::TranslateInstruction(const RdnaInstruction& decoded) {
case RdnaInstructionFamily::VOP3:
case RdnaInstructionFamily::VOP3P:
case RdnaInstructionFamily::VOPC:
translated = emitVector(instruction);
translated = emitVector(instruction) || emitInterpolation(instruction);
break;
case RdnaInstructionFamily::SMEM:
case RdnaInstructionFamily::MUBUF:

View File

@ -277,6 +277,7 @@ Throughout the project, every function at every stage either **does exactly what
- Without fetch-inactive, [`v_permlane16_b32` and `v_permlanex16_b32`](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) take a source lane that the host subgroup does not hold as inactive and keep the old value (0 with bound_ctrl); the console reads that lane's VGPR.
- On a host subgroup narrower than the wave, [`v_readlane_b32`](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) of a constant lane outside it builds only as lane 63 of a half-wave scan (DPP row_shr 1/2/4/8, `v_permlanex16_b32`, EXEC -1); other such reads throw.
- [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx).
- [v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16](../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp) read the 16-bit halves of each vertex's parameter through `VK_KHR_fragment_shader_barycentric` and throw without it, as fixed-function interpolation cannot split a 32-bit attribute. P10 and P20 are the vertex differences rounded to f16 to nearest even, which is not confirmed on hardware. v_interp_p1ll_f16 and v_interp_p1lv_f16 round the exact `P10 * I + P0` once to f32 and v_interp_p2_f16 rounds the exact `P20 * J + S2` once to f16, as measured on RDNA2 (#697); the exact sums are computed in f64, so these instructions need the Float64 capability. They follow the shader's float mode, measured on RDNA2 (gfx1035) in all eight combinations of `IEEE_MODE` and kept/flushed f32 and f16 denormals over 768 special and 1,024 random rows. With IEEE=1 a NaN operand is returned quieted; with IEEE=0 it is returned unquieted, except that v_interp_p2_f16's f16 result is always quiet. Flushed f32 denormals flush I/J, v_interp_p2_f16's f32 S2 and the f32 result; flushed f16 denormals flush P0, P10/P20, v_interp_p1lv_f16's f16 S2 and the f16 result. Denormal modes 1 and 2 (flush only inputs or only outputs) and rounding other than to nearest even are not measured and throw. An unknown mode follows IEEE=0 with denormals kept. Lavapipe has no barycentrics, so the draw test is skipped in CI; `agc_driver_interpolation_modes` checks the arithmetic in every mode against the hardware rows on any device with Float64.
- [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice.
- [SINT storage images](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) of 8- and 16-bit components saturate each stored 32-bit value to the component range, as adriadam10 measured for image stores on RDNA2 (gfx1035) in #944 (448 rows match); the PS5's gfx1013 is not measured.
- [`StoreTexel`](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) writes channel 1 of a two-channel format from the data slot whose DST_SEL is Y, for SINT and UINT alike. With DST_SEL X,Y,Z,W, gfx1035 writes it from the W slot, as adriadam10 measured in #944.
@ -285,7 +286,7 @@ Throughout the project, every function at every stage either **does exactly what
- A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown.
- A [COND_INDIRECT_BUFFER](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (`sceAgcCbBranch`) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. [sceAgcCbBranch](../../core/libs/prx/libSceAgc/Cb/src/Branch.cpp) does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords.
- [SET_*_REG_INDIRECT](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write.
- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). Only `IEEE_MODE` is read so far, by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`. With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. IEEE=1 with an output modifier throws. The f16 and f64 min/max/med3, NaN quieting in arithmetic, `DX10_CLAMP`, `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none.
- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, and the whole mode by the 16-bit interpolation instructions (see their entry). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. IEEE=1 with an output modifier throws. The f16 and f64 min/max/med3, NaN quieting in arithmetic, `DX10_CLAMP`, `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none.
- [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled.
- [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same.
- [v_fma_f16, v_pk_fma_f16 and v_pk_fmac_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) are an f32 FMA rounded to f16, which rounds twice: 0x62e9 * 0xd159 + 0x23ec gives 0xf89e, the hardware 0xf89f.