mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 17:13:13 -05:00
The integer emulation of the round-to-nearest-even float-to-half conversion is exact but costly: a compute shader with about 3600 conversions never finished compiling on NVIDIA. When the device enables shaderFloat16 and guarantees shaderRoundingModeRTEFloat16, shaderDenormPreserveFloat16 and shaderSignedZeroInfNanPreserveFloat16, it advertises Float16, RoundingModeRTE and DenormPreserve, and the recompiler emits OpFConvert under the 16-bit RoundingModeRTE, DenormPreserve and SignedZeroInfNanPreserve execution modes instead. NaNs keep the emulated quiet NaN with the high payload bits. Devices without these guarantees keep the emulation. Graphics validation accepts the RoundingModeRTE, DenormPreserve and SignedZeroInfNanPreserve execution modes for 16-, 32- and 64-bit floats, so vertex and pixel shaders with a native conversion still draw. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
98 lines
4.0 KiB
C++
98 lines
4.0 KiB
C++
#include "Recompiler.hpp"
|
|
#include <spirv/unified1/spirv.hpp>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <exception>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
using namespace ShaderRecompiler;
|
|
|
|
void require(bool condition, const std::string& message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
constexpr std::array<std::uint32_t, 4> CvtF16F32Store{0x7e021500u, 0xe0700000u, 0x80000100u, 0xbf810000u};
|
|
|
|
std::vector<std::uint32_t> recompile(std::span<const std::uint32_t> capabilities) {
|
|
const std::array<std::uint32_t, 4> userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu};
|
|
const std::array<std::string_view, 1> extensions{"SPV_KHR_storage_buffer_storage_class"};
|
|
RecompileRequest request{};
|
|
request.shader = {ShaderStage::Compute, 0x20000u, CvtF16F32Store, 0, {}};
|
|
request.context.waveSize = 64;
|
|
request.context.userDataBaseRegister = 0;
|
|
request.context.userData = userData;
|
|
request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
|
|
request.target.vulkanVersion = 0x00403000u;
|
|
request.target.spirvVersion = 0x00010600u;
|
|
request.target.subgroupSize = 64;
|
|
request.target.bdaAbiVersion = 1;
|
|
request.target.supportedCapabilities = capabilities;
|
|
request.target.supportedExtensions = extensions;
|
|
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
|
|
request.target.maxWorkgroupInvocations = 1024;
|
|
request.target.maxWorkgroupSharedMemoryBytes = 49152;
|
|
request.layout = {0, 0, 0, 128};
|
|
request.useCache = false;
|
|
auto result = Recompile(request);
|
|
require(!result.spirv.empty(), "v_cvt_f16_f32 did not recompile");
|
|
return std::move(result.spirv);
|
|
}
|
|
|
|
struct Usage {
|
|
std::size_t floatConverts = 0;
|
|
bool float16Capability = false;
|
|
bool roundingModeRte16 = false;
|
|
bool denormPreserve16 = false;
|
|
};
|
|
|
|
Usage scan(std::span<const std::uint32_t> words) {
|
|
Usage usage;
|
|
for (std::size_t cursor = 5; cursor < words.size();) {
|
|
const auto count = words[cursor] >> 16u;
|
|
const auto op = static_cast<spv::Op>(words[cursor] & 0xffffu);
|
|
require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction");
|
|
if (op == spv::OpFConvert) ++usage.floatConverts;
|
|
if (op == spv::OpCapability && words[cursor + 1] == spv::CapabilityFloat16) usage.float16Capability = true;
|
|
if (op == spv::OpExecutionMode && count == 4 && words[cursor + 3] == 16u) {
|
|
if (words[cursor + 2] == spv::ExecutionModeRoundingModeRTE) usage.roundingModeRte16 = true;
|
|
if (words[cursor + 2] == spv::ExecutionModeDenormPreserve) usage.denormPreserve16 = true;
|
|
}
|
|
cursor += count;
|
|
}
|
|
return usage;
|
|
}
|
|
|
|
void verifyNative() {
|
|
const std::array<std::uint32_t, 5> capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityFloat16, spv::CapabilityRoundingModeRTE, spv::CapabilityDenormPreserve};
|
|
const auto usage = scan(recompile(capabilities));
|
|
require(usage.floatConverts != 0 && usage.float16Capability, "a device that rounds 16-bit floats to nearest even did not get OpFConvert to f16");
|
|
require(usage.roundingModeRte16 && usage.denormPreserve16, "native f16 conversion without the 16-bit RoundingModeRTE and DenormPreserve execution modes");
|
|
}
|
|
|
|
void verifyEmulated() {
|
|
const std::array<std::uint32_t, 3> withoutRte{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityFloat16};
|
|
const auto usage = scan(recompile(withoutRte));
|
|
require(usage.floatConverts == 0 && !usage.float16Capability && !usage.roundingModeRte16, "a device without RoundingModeRTE for 16-bit floats got native f16 conversion");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
verifyNative();
|
|
verifyEmulated();
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "Float16Conversion: %s\n", error.what());
|
|
return 1;
|
|
}
|
|
std::puts("Float16Conversion: native and emulated f16 conversion selected by device capabilities");
|
|
return 0;
|
|
}
|