From 0cbc9586bc3f207e2bd629209cfb628f0cb41a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 8 Oct 2026 22:17:07 +0200 Subject: [PATCH] feat(shader): carry the shader float mode into the recompile request Co-Authored-By: Claude --- .../include/Driver/Shaders/ShaderRegistry.hpp | 2 + .../src/Driver/Dispatch/Dispatch.cpp | 2 +- .../Execution/src/Driver/Draw/DrawCapture.cpp | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 21 ++++++++++- .../libSceAgcDriver/tests/ShaderDiskCache.cpp | 27 ++++++++++++++ .../libSceAgcDriver/tests/ShaderMemory.cpp | 4 +- .../libs/prx/libSceAgcDriver/tests/Submit.cpp | 37 +++++++++++++++++++ core/shader/recompiler/CacheKey.hpp | 9 +++++ .../ControlFlow/src/RequestSerializer.cpp | 19 +++++++++- core/shader/recompiler/Recompiler.hpp | 10 +++++ docs/dev/TechnicalDebt.md | 1 + 11 files changed, 126 insertions(+), 8 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index a1803aacf..846729868 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" #include +#include #include #include #include @@ -70,6 +71,7 @@ struct ShaderSnapshot { std::shared_ptr ReadRawComputeShader(std::uint64_t address); std::uint64_t NullPixelProgramAddress(); +std::optional RegisteredFloatMode(const ShaderSnapshot& snapshot); void PublishRegisteredShader(std::shared_ptr& registry, const std::shared_ptr& snapshot); void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp index a30584839..b0909f721 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -67,7 +67,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } ShaderRecompiler::RecompileRequest request{ {ShaderRecompiler::ShaderStage::Compute, address, std::span(snapshot.code).subspan(codeOffset), snapshot.headerAddress, snapshot.header}, - {(packet[4] & 0x8000u) != 0 ? 32u : 64u, 0, userData, compute, std::nullopt, std::nullopt, memory}, + {(packet[4] & 0x8000u) != 0 ? 32u : 64u, 0, userData, compute, std::nullopt, std::nullopt, memory, RegisteredFloatMode(snapshot)}, localDevice->ComputeTarget((packet[4] & 0x8000u) != 0 ? 32u : 64u), {0, 0, 0, 128} }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp index b104445fa..97f85726f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -18,7 +18,7 @@ ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, st const auto waveSize = program.binary.stage == Stage::Fragment ? graphics.stages.fragmentWaveSize : graphics.stages.vertexWaveSize; ShaderRecompiler::RecompileRequest request{ program.binary, - {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory}, + {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory, RegisteredFloatMode(*program.snapshot)}, localDevice->Target(), {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes) - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}} diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index f899d1654..109b381a7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -235,6 +235,23 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s } throw std::runtime_error("AGC driver: prepared shader artifact is missing for the requested static ABI: address=" + std::to_string(request.shader.codeAddress) + " stage=" + std::to_string(static_cast(request.shader.stage)) + " wave=" + std::to_string(request.context.waveSize) + " pushOffset=" + std::to_string(request.layout.pushConstantOffsetBytes) + " pushCapacity=" + std::to_string(request.layout.pushConstantSizeBytes) + " preparedLayouts=" + layouts); } +std::optional RegisteredFloatMode(const ShaderSnapshot& snapshot) { + if (snapshot.registeredState == nullptr) return std::nullopt; + std::uint32_t rsrc1; + std::uint32_t fp16OverflowBit; + switch (snapshot.type) { + case 0: rsrc1 = 0x212; fp16OverflowBit = 26; break; + case 1: rsrc1 = 0x00a; fp16OverflowBit = 29; break; + case 2: case 4: case 6: rsrc1 = 0x08a; fp16OverflowBit = 31; break; + case 5: case 7: rsrc1 = 0x10a; fp16OverflowBit = 30; break; + default: return std::nullopt; + } + const auto found = snapshot.registeredState->shader.find(rsrc1); + if (found == snapshot.registeredState->shader.end()) return std::nullopt; + const auto value = found->second; + return ShaderRecompiler::ShaderFloatMode{(value >> 12u) & 0xffu, ((value >> 21u) & 1u) != 0u, ((value >> 23u) & 1u) != 0u, ((value >> fp16OverflowBit) & 1u) != 0u}; +} + namespace { template @@ -352,7 +369,7 @@ std::vector PrepareRegistered(const ShaderSnapshot& snap auto graph = ShaderRecompiler::GraphBuilder{}.Build(decoded, &swappc); ShaderRecompiler::Structurizer{}.Structurize(graph); const std::array memory{{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}}; - ShaderRecompiler::RecompileRequest request{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, userData, compute, pixel, vertex, memory}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics}; + ShaderRecompiler::RecompileRequest request{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, userData, compute, pixel, vertex, memory, RegisteredFloatMode(snapshot)}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics}; if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes; std::vector entries; const auto append = [&] { @@ -404,7 +421,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize; std::optional vertex; if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true); - ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory}, target, {0, 0, pushOffset, capacity - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; + ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, pushOffset, capacity - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); std::shared_ptr handle; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 38ca0eb78..3facf9ad5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -6,6 +6,7 @@ #include "Optimization/ResourceProgram.hpp" #include "Optimization/DescriptorBindingBuilder.hpp" #include "ShaderCacheDirectory.hpp" +#include "ControlFlow/RequestSerializer.hpp" #include #include #include @@ -394,6 +395,21 @@ void verifyKeySensitivity() { changes("the first binding", [](SampleRequest& sample) { sample.request.layout.firstBinding = 1; }); changes("the push constant offset", [](SampleRequest& sample) { sample.request.layout.pushConstantOffsetBytes = 16; }); changes("the push constant size", [](SampleRequest& sample) { sample.request.layout.pushConstantSizeBytes = 64; }); + changes("a float mode", [](SampleRequest& sample) { sample.request.context.floatMode = ShaderFloatMode{}; }); + const ShaderFloatMode astroMode{0xc0u, true, false, false}; + const auto withMode = [&](const ShaderFloatMode& mode) { + SampleRequest sample; + sample.request.context.floatMode = mode; + return std::pair{sample.Key(), RecompileCacheKey::ContextHash(sample.request)}; + }; + const auto astro = withMode(astroMode); + require(withMode(astroMode) == astro, "the float mode key is not deterministic"); + for (const auto& [what, mode] : std::initializer_list>{ + {"FLOAT_MODE", {0x00u, true, false, false}}, {"DX10_CLAMP", {0xc0u, false, false, false}}, + {"IEEE_MODE", {0xc0u, true, true, false}}, {"FP16_OVFL", {0xc0u, true, false, true}}}) { + const auto changed = withMode(mode); + require(changed.first != astro.first && changed.second != astro.second, std::string("the key ignores ") + what); + } SampleRequest moved; moved.userData[0] ^= 0x10000u; @@ -445,6 +461,16 @@ void verifyKeySensitivity() { require(fragment.Key() != fragmentKey, "integer fragment output reused a float interface"); } +void verifyFloatModeSerialization() { + const RequestSerializer serializer; + SampleRequest sample; + require(!serializer.Deserialize(serializer.Serialize(sample.request)).request.context.floatMode.has_value(), "an unknown float mode came back from serialization"); + const ShaderFloatMode mode{0x04u, false, true, true}; + sample.request.context.floatMode = mode; + const auto back = serializer.Deserialize(serializer.Serialize(sample.request)); + require(back.request.context.floatMode == mode, "the float mode did not survive serialization"); +} + void verifyStore() { require(ShaderDiskCache::Enabled(), "the disk cache is not enabled"); SampleRequest sample; @@ -996,6 +1022,7 @@ int main(int argc, char** argv) { verifyEntryRoundTrip(); verifyArtifactStorageIsolation(); verifyKeySensitivity(); + verifyFloatModeSerialization(); verifyStore(); verifyAcrossProcesses(argv[0]); verifyEmissionFailureMemo(); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 767af1e14..707e06f2c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -916,12 +916,12 @@ void verifyPixelRequestSerialization() { minimal.context.waveSize = 64; minimal.context.pixel = ShaderPixelStageInfo{}; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQQwAAAA=", "new requests did not use serialization version 12"); + require(requestPrefix(encoded, 8u) == "NVNQQQ0AAAA=", "new requests did not use serialization version 13"); constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u; for (std::size_t bytes = 0; bytes < 8u; ++bytes) { expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated version-12 pixel mapping was accepted"); } - for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQQ0AAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQQ4AAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 42849ea44..e774ae46e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -480,6 +481,41 @@ void testShaderHeaderAlignment() { refused(expectFailure([&] { AgcDriverRegisterShader_nid_postfix(at(4, truncated)); }), "smaller than its fixed fields"); } +void testRegisteredFloatMode() { + using AgcDriver::DriverDetail::RegisteredFloatMode; + using AgcDriver::DriverDetail::ShaderSnapshot; + using AgcDriver::DriverDetail::RegisteredShaderState; + struct Stage { + std::uint8_t type; + std::uint32_t rsrc1; + std::uint32_t fp16OverflowBit; + }; + constexpr std::array stages{{{0, 0x212, 26}, {1, 0x00a, 29}, {2, 0x08a, 31}, {4, 0x08a, 31}, {6, 0x08a, 31}, {5, 0x10a, 30}, {7, 0x10a, 30}}}; + constexpr std::array otherBits{26, 29, 30, 31}; + for (const auto& stage : stages) { + const auto mode = [&](std::uint32_t value) { + ShaderSnapshot snapshot{0x20000, 0, stage.type, {}, {}}; + auto state = std::make_shared(); + state->shader.emplace(stage.rsrc1, value); + snapshot.registeredState = state; + return RegisteredFloatMode(snapshot); + }; + const auto name = "stage type " + std::to_string(stage.type); + const auto astro = mode((0xc0u << 12u) | (1u << 21u) | 0x3fu); + check(astro.has_value() && astro->floatMode == 0xc0u && astro->dx10Clamp && !astro->ieeeMode && !astro->fp16Overflow, (name + ": FLOAT_MODE 0xc0 with DX10_CLAMP decoded wrong").c_str()); + const auto ieee = mode(1u << 23u); + check(ieee && ieee->ieeeMode && ieee->floatMode == 0u && !ieee->dx10Clamp, (name + ": IEEE_MODE decoded wrong").c_str()); + check(mode(1u << stage.fp16OverflowBit)->fp16Overflow, (name + ": FP16_OVFL not read from bit " + std::to_string(stage.fp16OverflowBit)).c_str()); + for (const auto bit : otherBits) { + if (bit != stage.fp16OverflowBit) check(!mode(1u << bit)->fp16Overflow, (name + ": bit " + std::to_string(bit) + " read as FP16_OVFL").c_str()); + } + ShaderSnapshot missing{0x20000, 0, stage.type, {}, {}}; + check(!RegisteredFloatMode(missing).has_value(), (name + ": a snapshot without registered state has a float mode").c_str()); + missing.registeredState = std::make_shared(); + check(!RegisteredFloatMode(missing).has_value(), (name + ": a missing RSRC1 has a float mode").c_str()); + } +} + void testWorkerFailure() { std::array words{0xc0031500, 1, 1, 1, 0x41}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; @@ -580,6 +616,7 @@ int main() { testWaitFreeSubmissionTheCpuWaitsFor(); testMultiSubmissions(); testShaderHeaderAlignment(); + testRegisteredFloatMode(); testWorkerFailure(); check(expectFailure([] { LibcRunShutdown_nid_postfix(); }).find("required shader register") != std::string::npos, "shutdown lost worker failure"); std::puts("AGC driver submit tests passed"); diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index 3afac6b4e..4207ad6ab 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -51,6 +51,7 @@ public: append(key, request.context.compute); append(key, request.context.pixel); append(key, request.context.vertex); + append(key, request.context.floatMode); appendMesh(key, request); appendTessellation(key, request); std::uint64_t hash = 0xcbf29ce484222325ull; @@ -82,6 +83,7 @@ private: append(key, request.context.compute); append(key, request.context.pixel); append(key, request.context.vertex); + append(key, request.context.floatMode); appendMesh(key, request); appendTessellation(key, request); append(key, request.target); @@ -133,6 +135,13 @@ private: for (const unsigned char byte : value) append(key, byte); } + static void append(std::vector& key, const ShaderFloatMode& value) { + append(key, value.floatMode); + append(key, value.dx10Clamp); + append(key, value.ieeeMode); + append(key, value.fp16Overflow); + } + static void append(std::vector& key, const ShaderComputeStageInfo& value) { append(key, value.numThreads); append(key, value.ldsSizeDwords); diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index b6bfdbe4d..48386cd20 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -519,6 +519,13 @@ void writeGuestContext(Writer& writer, const GuestContext& context) { writer.WriteU64(region.guestAddress); writer.WriteBytes(region.bytes); } + writer.WriteBool(context.floatMode.has_value()); + if (context.floatMode.has_value()) { + writer.WriteU32(context.floatMode->floatMode); + writer.WriteBool(context.floatMode->dx10Clamp); + writer.WriteBool(context.floatMode->ieeeMode); + writer.WriteBool(context.floatMode->fp16Overflow); + } } GuestContext readGuestContext(Reader& reader, DeserializedRequest& result, std::uint32_t version) { @@ -551,6 +558,14 @@ GuestContext readGuestContext(Reader& reader, DeserializedRequest& result, std:: result.memory.push_back(region); } context.memory = result.memory; + if (version >= 13u && reader.ReadBool()) { + ShaderFloatMode mode; + mode.floatMode = reader.ReadU32(); + mode.dx10Clamp = reader.ReadBool(); + mode.ieeeMode = reader.ReadBool(); + mode.fp16Overflow = reader.ReadBool(); + context.floatMode = mode; + } return context; } @@ -699,7 +714,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(12u); + writer.WriteU32(13u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -726,7 +741,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 12u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 13u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version); diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index d1b5bd730..574fc96c2 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -155,6 +155,15 @@ struct ShaderVertexStageInfo { bool fetchEmbedded; }; +struct ShaderFloatMode { + std::uint32_t floatMode = 0; + bool dx10Clamp = false; + bool ieeeMode = false; + bool fp16Overflow = false; + + bool operator==(const ShaderFloatMode&) const = default; +}; + struct GuestContext { std::uint32_t waveSize; std::uint32_t userDataBaseRegister; @@ -163,6 +172,7 @@ struct GuestContext { std::optional pixel; std::optional vertex; std::span memory; + std::optional floatMode; }; struct MeshTargetLimits { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index c209de0e9..749d0e100 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -284,6 +284,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, but no translation reads it yet (#981). Every float semantic still follows the single mode it was measured or written in, 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. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.