shader: emit float min/max as host instructions on AMD GPUs (#1761)

* shader: emit float min/max as host instructions on AMD GPUs

V_MIN_F32, V_MAX_F32, V_MIN3_F32 and V_MAX3_F32 were emulated with a
classify-and-select sequence of about a dozen instructions each, to give
the guest's NaN and signed-zero results on any host. AMD GPUs execute
GLSL.std.450 NMin/NMax with those same rules, so emit them directly when
the device is AMD and supports VK_KHR_shader_float_controls2. Each
instruction is decorated FPFastMathMode None, since
SignedZeroInfNanPreserve does not cover extended instructions. MED3 keeps
its exact emulation. KYTY_EMULATED_FLOAT_MIN_MAX=1 keeps the emulation.

In SAROS the two heaviest compute shaders get 37% and 59% faster on an
RX 9070 XT (RADV), about 4 ms of GPU time per frame.

Adds VectorMinMaxF32RuntimeEdges (runtime operands, so nothing is folded)
and a --float-min-max-only mode that runs it and
VectorMinMaxF32NanAndSignedZeroEdges with the emulation and, where
supported, with host instructions.

Developed with AI assistance (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shader: gate host float min/max on RADV and check the emitted instructions

* shader: drop float controls 2 from host float min/max

* shader: drop the float min/max env override and atomic flag

* shader: pass host capabilities to the SPIR-V emitter as a profile built once per device

* shader: enable host float min/max on AMD GPUs

---------

Co-authored-by: arda <ardaeska@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
Arda Sahin
2026-10-21 19:36:08 +00:00
committed by GitHub
parent c07ad621f2
commit 286481889f
11 changed files with 141 additions and 23 deletions
+2
View File
@@ -657,6 +657,8 @@ if(BUILD_TESTING)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>) add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME shader_recompiler_tessellation add_test(NAME shader_recompiler_tessellation
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
add_test(NAME shader_recompiler_float_min_max
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
add_test(NAME shader_recompiler_alignbyte add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization add_test(NAME graphics_pipeline_rasterization
@@ -308,7 +308,7 @@ struct PipelineCache::ProgramCache {
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options, auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
specialization, push_data_start_dword); specialization, push_data_start_dword);
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv, if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv,
options.maintenance8_enabled)) { options.profile->maintenance8)) {
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv); DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label, EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
options.shader_hash); options.shader_hash);
@@ -398,7 +398,7 @@ struct PipelineCache::ProgramCache {
} }
ShaderRecompiler::CompileOptions options; ShaderRecompiler::CompileOptions options;
options.stage = stage; options.stage = stage;
options.maintenance8_enabled = graphics.maintenance8_enabled; options.profile = &profile;
options.shader_hash = params.hash; options.shader_hash = params.hash;
options.user_data = user_data; options.user_data = user_data;
options.back_code = params.back_code; options.back_code = params.back_code;
@@ -480,7 +480,13 @@ struct PipelineCache::ProgramCache {
return permutation.handle; return permutation.handle;
} }
explicit ProgramCache(GraphicContext& graphics): graphics(graphics) { explicit ProgramCache(GraphicContext& graphics)
: graphics(graphics),
// AMD GPUs (vendor 0x1002) run NMin/NMax as v_min/v_max, which give the guest's NaN and
// signed-zero results; GLSL.std.450 alone does not order -0 below +0.
profile {.maintenance8 = graphics.maintenance8_enabled,
.native_float_min_max =
graphics.physical_device_properties.vendorID == 0x1002u} {
lookup_key.static_state.reserve(MaxStaticKeyWords); lookup_key.static_state.reserve(MaxStaticKeyWords);
} }
~ProgramCache() { ~ProgramCache() {
@@ -495,6 +501,7 @@ struct PipelineCache::ProgramCache {
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs; std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
ProgramKey lookup_key; ProgramKey lookup_key;
GraphicContext& graphics; GraphicContext& graphics;
const ShaderRecompiler::Spirv::Profile profile;
uint64_t next_shader_id = 0; uint64_t next_shader_id = 0;
}; };
@@ -930,8 +930,8 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash); GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, *options.profile,
options.maintenance8_enabled, options.emit_debug_info); options.emit_debug_info);
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64 LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
" elapsed_ms=%" PRIu64 "\n", " elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash, GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
@@ -3,6 +3,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h" #include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
@@ -23,7 +24,7 @@ struct CompileOptions {
bool dump_ir = true; bool dump_ir = true;
bool early_dump = false; bool early_dump = false;
bool emit_debug_info = false; bool emit_debug_info = false;
bool maintenance8_enabled = false; const Spirv::Profile* profile = &Spirv::DefaultProfile;
const char* dump_label = nullptr; const char* dump_label = nullptr;
std::span<const uint32_t> user_data; std::span<const uint32_t> user_data;
std::span<const uint32_t> back_code; std::span<const uint32_t> back_code;
@@ -204,7 +204,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
} // namespace } // namespace
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword, bool maintenance8_enabled, uint32_t push_data_start_dword, const Profile& profile,
bool emit_debug_info) { bool emit_debug_info) {
using namespace Emitter; using namespace Emitter;
@@ -220,7 +220,7 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
} }
IR::ValidateProgram(program, true); IR::ValidateProgram(program, true);
program.bindings = {.push_data_start_dword = push_data_start_dword}; program.bindings = {.push_data_start_dword = push_data_start_dword};
EmitterState state(program, input_info, maintenance8_enabled); EmitterState state(program, input_info, profile);
if (emit_debug_info) { if (emit_debug_info) {
char name[64]; char name[64];
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one", std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
@@ -9,11 +9,19 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv { namespace Libs::Graphics::ShaderRecompiler::Spirv {
// Host device capabilities that change the emitted SPIR-V. Built once per device.
struct Profile {
bool maintenance8 = false; // Dynamic texel offsets on image samples.
bool native_float_min_max = false; // NMin/NMax give the guest's NaN and signed-zero results.
};
inline constexpr Profile DefaultProfile {};
// Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently. // Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently.
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword = 0, uint32_t push_data_start_dword = 0,
bool maintenance8_enabled = false, const Profile& profile = DefaultProfile,
bool emit_debug_info = false); bool emit_debug_info = false);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv } // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -113,6 +113,12 @@ static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs, u
} }
uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) { uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
if (state.profile.native_float_min_max) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpExtInst, TypeF32(state), result, GlslStd450(state),
max_value ? GLSLstd450NMax : GLSLstd450NMin, lhs, rhs);
return result;
}
return EmitMinMaxF32(state, lhs, rhs, EmitBitcastF32ToU32(state, lhs), return EmitMinMaxF32(state, lhs, rhs, EmitBitcastF32ToU32(state, lhs),
EmitBitcastF32ToU32(state, rhs), max_value); EmitBitcastF32ToU32(state, rhs), max_value);
} }
@@ -1057,8 +1057,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operands[1] = AddressF32(ctx, mem, *address, layout.bias); operands[1] = AddressF32(ctx, mem, *address, layout.bias);
operand_count = 2; operand_count = 2;
} }
if (layout.offset != NoImageComponent && if (layout.offset != NoImageComponent && !offset_value.IsImmediate() &&
!offset_value.IsImmediate() && !state.maintenance8_enabled) { !state.profile.maintenance8) {
ctx.Fail(inst, "requires maintenance8 for a dynamic sample texel offset"); ctx.Fail(inst, "requires maintenance8 for a dynamic sample texel offset");
} }
const auto samplers = LoadImageSamplers(ctx, inst); const auto samplers = LoadImageSamplers(ctx, inst);
@@ -5,6 +5,7 @@
#include "common/stringUtils.h" #include "common/stringUtils.h"
#include "graphics/shader/recompiler/BufferFormat.h" #include "graphics/shader/recompiler/BufferFormat.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h" #include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
@@ -73,9 +74,9 @@ struct ImageDefinition {
}; };
struct EmitterState { struct EmitterState {
EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_, bool maintenance8_enabled_) EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_, const Profile& profile_)
: builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u), : builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u),
program(program_), input_info(input_info_), maintenance8_enabled(maintenance8_enabled_) { program(program_), input_info(input_info_), profile(profile_) {
if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) { if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) {
lds_storage_class = spv::StorageClassWorkgroup; lds_storage_class = spv::StorageClassWorkgroup;
} }
@@ -88,7 +89,7 @@ struct EmitterState {
Builder builder; Builder builder;
IR::Program& program; IR::Program& program;
ShaderStageInputInfo input_info; ShaderStageInputInfo input_info;
bool maintenance8_enabled; const Profile& profile;
uint32_t debug_source = 0; uint32_t debug_source = 0;
uint32_t void_type = 0; uint32_t void_type = 0;
uint32_t bool_type = 0; uint32_t bool_type = 0;
+98 -5
View File
@@ -1313,6 +1313,9 @@ struct BdaMapping {
u32 backing_offset = 0; u32 backing_offset = 0;
}; };
using ShaderProfile = ShaderRecompiler::Spirv::Profile;
constexpr ShaderProfile Maintenance8Profile{.maintenance8 = true};
struct TestCase { struct TestCase {
const char *name = ""; const char *name = "";
std::vector<u32> code; std::vector<u32> code;
@@ -1340,6 +1343,7 @@ struct TestCase {
std::vector<u32> expected_storage_image_r32ui; std::vector<u32> expected_storage_image_r32ui;
std::vector<std::string> required_spirv; std::vector<std::string> required_spirv;
std::vector<std::string> forbidden_spirv; std::vector<std::string> forbidden_spirv;
const ShaderProfile *profile = &Maintenance8Profile;
ShaderComputeInputInfo compute_info = [] { ShaderComputeInputInfo compute_info = [] {
ShaderComputeInputInfo info{}; ShaderComputeInputInfo info{};
info.lds_size_dwords = 1024; info.lds_size_dwords = 1024;
@@ -1672,7 +1676,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
} }
ShaderRecompiler::CompileOptions options; ShaderRecompiler::CompileOptions options;
options.stage = ShaderType::Compute; options.stage = ShaderType::Compute;
options.maintenance8_enabled = true; options.profile = test.profile;
options.dump_ir = true; options.dump_ir = true;
auto compute_info = test.compute_info; auto compute_info = test.compute_info;
compute_info.host_subgroup_size = host_subgroup_size; compute_info.host_subgroup_size = host_subgroup_size;
@@ -1745,7 +1749,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
result.program.shader_info_complete = false; result.program.shader_info_complete = false;
ShaderRecompiler::IR::CollectShaderInfo(result.program, options.input_info); ShaderRecompiler::IR::CollectShaderInfo(result.program, options.input_info);
result.spirv = ShaderRecompiler::Spirv::EmitProgram( result.spirv = ShaderRecompiler::Spirv::EmitProgram(
result.program, options.input_info, 0u, options.maintenance8_enabled); result.program, options.input_info, 0u, *options.profile);
const auto shader_data = result.program.bindings.descriptor_counts[ const auto shader_data = result.program.bindings.descriptor_counts[
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::ShaderData)]; static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::ShaderData)];
Require(test.name, "binding layout", Require(test.name, "binding layout",
@@ -1862,7 +1866,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
ShaderRecompiler::CompileOptions options; ShaderRecompiler::CompileOptions options;
options.stage = ShaderType::Pixel; options.stage = ShaderType::Pixel;
options.maintenance8_enabled = true; options.profile = &Maintenance8Profile;
options.dump_ir = false; options.dump_ir = false;
options.input_info.pixel = &pixel_info; options.input_info.pixel = &pixel_info;
options.user_data = user_data; options.user_data = user_data;
@@ -2001,6 +2005,12 @@ class VulkanHarness {
public: public:
VulkanHarness() { Init(); } VulkanHarness() { Init(); }
~VulkanHarness() { Destroy(); } ~VulkanHarness() { Destroy(); }
// Host float min/max is used on AMD GPUs, as in Kyty.
[[nodiscard]] bool SupportsNativeFloatMinMax() const {
vk::PhysicalDeviceProperties properties;
m_physical_device.getProperties(&properties);
return properties.vendorID == 0x1002u;
}
VulkanHarness(const VulkanHarness &) = delete; VulkanHarness(const VulkanHarness &) = delete;
VulkanHarness &operator=(const VulkanHarness &) = delete; VulkanHarness &operator=(const VulkanHarness &) = delete;
@@ -10799,7 +10809,7 @@ public:
ShaderRecompiler::CompileOptions options; ShaderRecompiler::CompileOptions options;
options.wave_size = wave_size; options.wave_size = wave_size;
options.dump_ir = false; options.dump_ir = false;
options.maintenance8_enabled = true; options.profile = &Maintenance8Profile;
input.host_subgroup_size = SubgroupSize(); input.host_subgroup_size = SubgroupSize();
options.input_info.compute = &input; options.input_info.compute = &input;
options.user_data = user_data; options.user_data = user_data;
@@ -27863,6 +27873,66 @@ TestCase VectorMinMaxF32NanAndSignedZeroEdges() {
O::V_MAX3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; O::V_MAX3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
} }
// MIN/MAX/MIN3/MAX3 on runtime inputs, so no compiler can fold them. The expected bits follow
// the emulated rules: a NaN operand yields the other operand and signed zeros are ordered.
// Denormal operands are left out; their expected results are not pinned down here.
TestCase VectorMinMaxF32RuntimeEdges(u32 wave) {
using O = ShaderOpcode;
const auto nan = [](u32 v) { return (v & 0x7fffffffu) > 0x7f800000u; };
const auto zero = [](u32 v) { return (v & 0x7fffffffu) == 0u; };
const auto value = [](u32 v) { return std::bit_cast<float>(v); };
const auto min2 = [&](u32 a, u32 b) {
if (nan(a)) return b;
if (nan(b)) return a;
if (zero(a) && zero(b)) return a | b;
return value(a) < value(b) ? a : b;
};
const auto max2 = [&](u32 a, u32 b) {
if (nan(a)) return b;
if (nan(b)) return a;
if (zero(a) && zero(b)) return a & b;
return value(a) >= value(b) ? a : b;
};
constexpr u32 cases[][3] = {
{0x40000000u, 0x40400000u, 0x3f800000u}, {0x7fc00000u, 0x40000000u, 0xbf800000u},
{0x40000000u, 0x7fc00000u, 0x3f800000u}, {0x7fa00001u, 0x40000000u, 0x40400000u},
{0x40000000u, 0x7fa00001u, 0xc0000000u}, {0x80000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u},
{0xff800000u, 0x7f800000u, 0x00000000u}, {0x7fc00000u, 0x00000000u, 0x80000000u},
{0xffc05678u, 0x3f800000u, 0x7fa00001u}, {0xbf800000u, 0xc0000000u, 0x7fc01234u}};
TestCase test;
test.name = "VectorMinMaxF32RuntimeEdges";
test.has_compute_info = true;
test.compute_info.wave_size = wave;
test.compute_info.float_mode = 0xf0; // Preserve FP32 input/output denormals.
test.compute_info.threads_num[0] = test.compute_info.threads_num[1] =
test.compute_info.threads_num[2] = 1u;
for (const auto &values : cases)
test.initial.insert(test.initial.end(), values, values + 3);
test.expected = test.initial;
auto &code = test.code;
for (u32 index = 0; index < std::size(cases); ++index) {
for (u32 operand = 0; operand < 3; ++operand) {
AppendVMovU32(&code, 30, (index * 3u + operand) * 4u);
AppendBufferLoadDword(&code, operand, 30);
}
const auto [a, b, c] = cases[index];
code.push_back(EncodeVop2(0x0f, 10, Vgpr(0), 1));
code.push_back(EncodeVop2(0x10, 11, Vgpr(0), 1));
AppendVop3(&code, 0x151, 12, Vgpr(0), Vgpr(1), Vgpr(2));
AppendVop3(&code, 0x154, 13, Vgpr(0), Vgpr(1), Vgpr(2));
for (u32 result = 0; result < 4; ++result)
AppendStoreVgpr(&code, 10 + result, static_cast<u32>(test.expected.size() + result));
test.expected.insert(test.expected.end(),
{min2(a, b), max2(a, b), min2(min2(a, b), c), max2(max2(a, b), c)});
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MIN_F32, O::V_MAX_F32,
O::V_MIN3_F32, O::V_MAX3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
TestCase VectorMed3F32NanUsesMin3Path(u32 wave) { TestCase VectorMed3F32NanUsesMin3Path(u32 wave) {
using O = ShaderOpcode; using O = ShaderOpcode;
// Runtime inputs keep every MED3 in the emitted module; compare raw result bits. // Runtime inputs keep every MED3 in the emitted module; compare raw result bits.
@@ -38144,7 +38214,8 @@ void CheckDynamicSampleOffsets(VulkanHarness &vulkan, u32 wave_size) {
"dynamic offsets expanded samples or lost signed six-bit components"); "dynamic offsets expanded samples or lost signed six-bit components");
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
if (wave_size == 32u) { if (wave_size == 32u) {
Require(test.name, "default feature gate", !ShaderRecompiler::CompileOptions{}.maintenance8_enabled, Require(test.name, "default feature gate",
!ShaderRecompiler::CompileOptions{}.profile->maintenance8,
"maintenance8 must reflect the enabled device feature"); "maintenance8 must reflect the enabled device feature");
ExpectFatal("DynamicSampleOffsetsRequireMaintenance8", [&] { ExpectFatal("DynamicSampleOffsetsRequireMaintenance8", [&] {
ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &test.compute_info}); ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &test.compute_info});
@@ -42983,6 +43054,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorMinMaxF32NanAndSignedZeroEdges); AddCase(VectorMinMaxF32NanAndSignedZeroEdges);
cases.push_back(VectorMed3F32NanUsesMin3Path(32)); cases.push_back(VectorMed3F32NanUsesMin3Path(32));
cases.push_back(VectorMed3F32NanUsesMin3Path(64)); cases.push_back(VectorMed3F32NanUsesMin3Path(64));
cases.push_back(VectorMinMaxF32RuntimeEdges(32));
cases.push_back(VectorMinMaxF32RuntimeEdges(64));
AddCase(VectorFloatConversionOps); AddCase(VectorFloatConversionOps);
AddCase(VectorFrexpF32Edges); AddCase(VectorFrexpF32Edges);
AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange); AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange);
@@ -48940,6 +49013,26 @@ int main(int argc, char **argv) {
RunCase(&vulkan, CvtPkrtzF16F32SdwaAndOutputModifiers()); RunCase(&vulkan, CvtPkrtzF16F32SdwaAndOutputModifiers());
return 0; return 0;
} }
if (argc == 2 && std::strcmp(argv[1], "--float-min-max-only") == 0) {
// Host min/max must keep the emulated results on AMD GPUs; other GPUs only
// compile it.
static constexpr ShaderProfile native_profile{.maintenance8 = true,
.native_float_min_max = true};
VulkanHarness vulkan;
const bool supported = vulkan.SupportsNativeFloatMinMax();
for (bool native : {false, true}) {
for (auto test :
{VectorMinMaxF32NanAndSignedZeroEdges(),
VectorMinMaxF32RuntimeEdges(32), VectorMinMaxF32RuntimeEdges(64)}) {
auto &spirv = native ? test.required_spirv : test.forbidden_spirv;
spirv = {" NMin ", " NMax "};
test.profile = native ? &native_profile : &Maintenance8Profile;
test.compile_only = native && !supported;
RunCase(&vulkan, test);
}
}
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--thread-dimensions-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--thread-dimensions-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
CheckComputeThreadDimensions(vulkan); CheckComputeThreadDimensions(vulkan);
+2 -2
View File
@@ -16350,8 +16350,8 @@ void CheckSpirvBlockSourceMapping(TestCompileResult &result) {
Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 && Check(SpirvInstructionOpcodeCount(result.spirv, spv::OpString) == 0 &&
SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0, SpirvInstructionOpcodeCount(result.spirv, spv::OpLine) == 0,
"default emission unexpectedly contains source mapping"); "default emission unexpectedly contains source mapping");
const auto mapped = Spirv::EmitProgram(result.program, options.input_info, 0, const auto mapped =
false, true); Spirv::EmitProgram(result.program, options.input_info, 0, {}, true);
CheckSpirvBinaryValidates(mapped); CheckSpirvBinaryValidates(mapped);
uint32_t source = 0; uint32_t source = 0;
uint32_t label = 0; uint32_t label = 0;