Compare commits

...

4 Commits

Author SHA1 Message Date
Devran Cosmo Uenal e0b682e4b2 AvPlayer: fix Crash Bandicoot 4 intro playback and green video padding (#848)
* AvPlayer: advance the shared audio-master clock after final audio frame

* AvPlayer: initialize padded NV12 planes with neutral black

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-26 20:39:46 +04:00
nmzik 16b83a034c Shader: execute RDNA2 FP64 shadow arithmetic and log denorm limits
Shader: disable FP64 denorm preservation controls for now
2026-09-26 15:15:35 +04:00
nmzik cf268af839 Shader: implement RDNA2 image atomic float maximum
Shader: implement RDNA2 image atomic float minimum

Co-authored-by: foufouadi <attar.foued@gmail.com>
Co-authored-by: frans <francois.att@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: Matthew Hurley <171001439+theantipopau@users.noreply.github.com>
2026-09-26 15:15:35 +04:00
Mehmet Cambaz fd2e15ee73 ci: run the recompiler unit tests (#447)
Build and run the CPU-only resource_tracking, scalar_provenance, resource_materialization, and shader_vertex_metadata tests on Windows, macOS, and Linux. Each test executable is excluded from the default build, so add it to the explicit CMake targets and the matching CTest selection. Leave the Vulkan-dependent shader_recompiler_compute suite for a separate CI setup.
2026-09-26 03:00:01 +04:00
27 changed files with 487 additions and 27 deletions
+11 -4
View File
@@ -112,12 +112,15 @@ jobs:
- name: Build
shell: cmd
run: |
cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests --parallel
cmake --build _Build/windows --target launcher ^
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
resource_tracking_tests scalar_provenance_tests ^
resource_materialization_tests shader_vertex_metadata_tests --parallel
- name: Test
shell: cmd
run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|lru_cache|virtual_memory_allocation)$"
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$"
- name: Install
shell: cmd
@@ -199,13 +202,15 @@ jobs:
run: |
cmake --build _Build/macos \
--target launcher audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|ime_dialog|lru_cache|virtual_memory_allocation)$'
-R '^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
- name: Install
shell: bash
@@ -380,13 +385,15 @@ jobs:
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|ime_dialog|lru_cache|page_manager|memory_tracker|virtual_memory_allocation)$'
-R '^(audio_out2_port|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
- name: Install
shell: bash
+1
View File
@@ -29,6 +29,7 @@ struct GraphicContext {
bool memory_budget_ext_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool sample_rate_shading_enabled = false;
// bool fp64_denorm_preserve = false; // Temporarily disabled.
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool supports_block_texel_view = false;
@@ -335,8 +335,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
resource.written && !resource.read && !resource.atomic;
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
resource.atomic;
const bool format_ok =
raw_sint_storage ||
raw_sint_storage || raw_float_atomic ||
(numeric_class != Prospero::TextureNumericClass::Unsupported &&
numeric_class != Prospero::TextureNumericClass::Sint &&
uint_resource == (numeric_class == Prospero::TextureNumericClass::Uint) &&
@@ -640,7 +642,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
pixel_format = depth_format->depth_attachment_format;
}
}
const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt
const auto storage_view_format = storage && (resource.atomic ||
format == Prospero::BufferFormat::k32SInt)
? vk::Format::eR32Uint
: SrgbStorageViewFormat(pixel_format);
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
@@ -564,8 +564,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
vk::PhysicalDeviceVulkan11Properties properties11 {};
properties11.pNext = &subgroup_size_control;
vk::PhysicalDeviceFloatControlsProperties float_controls {};
float_controls.pNext = &properties11;
vk::PhysicalDeviceProperties2 properties2 {};
properties2.pNext = &properties11;
properties2.pNext = &float_controls;
if (graphics.mesh_shader_enabled) {
subgroup_size_control.pNext = &graphics.mesh_shader_properties;
@@ -628,6 +630,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.shaderInt64 = VK_TRUE;
device_features.shaderFloat64 =
supported_features2.features.shaderFloat64 &&
float_controls.shaderSignedZeroInfNanPreserveFloat64 &&
float_controls.shaderRoundingModeRTEFloat32;
// if (device_features.shaderFloat64 && !float_controls.shaderDenormPreserveFloat64) {
// Log::WriteToConsoleAndLog(
// "WARNING: Vulkan device does not guarantee FP64 denormal preservation; "
// "continuing with native FP64 arithmetic. Very small values may be flushed to zero.\n");
// }
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
#if defined(__APPLE__)
@@ -198,6 +198,7 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
SpirvRequirements requirements {};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
requirements.float64 |= inst.GetType() == IR::Type::F64;
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
inst.GetType() == IR::Type::U64) {
requirements.buffer_int64_atomics = true;
@@ -291,9 +291,20 @@ uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0) {
return EmitF32ToU32(state, arg0, false);
}
uint32_t EmitConvertF32S32(EmitterState& state, uint32_t arg0) {
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
return EmitNative<spv::OpConvertSToF, IR::Type::F32>(state, signed_value);
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0) {
const auto converted = Unary(state, spv::OpFConvert, TypeF32(state), arg0);
const auto source = EmitNative<spv::OpCompositeExtract, IR::Type::U32>(
state, Unary(state, spv::OpBitcast, TypeU64(state), arg0), 1u);
const auto exponent = EmitAndConstant(state, source, 0x7ff00000u);
const auto overflow =
Binary(state, spv::OpLogicalAnd, TypeBool(state),
EmitCompareU32Constant(state, spv::OpUGreaterThan, exponent, 0x47e00000u),
EmitCompareU32Constant(state, spv::OpULessThan, exponent, 0x7ff00000u));
const auto clamped = EmitOrU32(state, EmitAndConstant(state, source, 0x80000000u),
ConstantU32(state, 0x7f7fffffu));
return EmitFlushF32DenormToSignedZero(
state, Select(state, TypeF32(state), overflow,
Unary(state, spv::OpBitcast, TypeF32(state), clamped), converted));
}
uint32_t EmitCompositeExtractU64(EmitterState& state, uint32_t arg0, IR::Value arg1) {
@@ -453,6 +464,11 @@ uint32_t EmitFPRecip32(EmitterState& state, uint32_t arg0) {
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), source);
}
uint32_t EmitFPRecip64(EmitterState& state, uint32_t arg0) {
const auto one = state.builder.Constant(spv::OpConstant, TypeF64(state), 0u, 0x3ff00000u);
return EmitNative<spv::OpFDiv, IR::Type::F64>(state, one, arg0);
}
uint32_t EmitFPRecipIFlag32(EmitterState& state, uint32_t arg0) {
// Integer-to-float inputs used by IFLAG cannot be denormal.
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), arg0);
@@ -877,7 +877,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return;
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != spv::OpNop) {
if (image_info.access == IR::ImageAccess::Atomic) {
const auto dimension = image.dimension;
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto pointer = state.builder.AllocateId();
@@ -887,6 +887,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
StorageImageDescriptorPointer(state, mem.resource),
CoordU32(ctx, mem, *address, dimension),
ConstantU32(state, 0));
if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
op == IR::ValueOpcode::ImageAtomicFMax32) {
return AtomicUpdate(state, pointer, IR::ResourceKind::Image,
[&](uint32_t old) {
return EmitFloatAtomicReplacement(
state, old, ctx.Arg(inst, 2),
op == IR::ValueOpcode::ImageAtomicFMax32);
});
}
const auto old = state.builder.AllocateId();
state.builder.AddFunction(atomic_opcode, TypeU32(state), old, pointer,
ConstantU32(state, spv::ScopeDevice),
@@ -13,13 +13,22 @@ inline constexpr auto EmitConvertU32U16 = EmitBitCastU16F16;
inline constexpr auto EmitConvertU32U8 = EmitBitCastU16F16;
inline constexpr auto EmitBitCastU32F32 = EmitBitCastU16F16;
EMIT_NATIVE(BitCastF32U32, OpBitcast, F32, uint32_t)
EMIT_NATIVE(BitCastU64F64, OpBitcast, U64, uint32_t)
EMIT_NATIVE(BitCastF64U64, OpBitcast, F64, uint32_t)
uint32_t EmitConvertU16U32(EmitterState& state, uint32_t arg0);
uint32_t EmitConvertU8U32(EmitterState& state, uint32_t arg0);
uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0);
inline constexpr auto EmitConvertF32F16 = EmitF16BitsToF32;
uint32_t EmitConvertS32F32(EmitterState& state, uint32_t arg0);
uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0);
uint32_t EmitConvertF32S32(EmitterState& state, uint32_t arg0);
template <IR::Type type>
uint32_t EmitConvertSigned32ToFloat(EmitterState& state, uint32_t arg0) {
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
return EmitNative<spv::OpConvertSToF, type>(state, signed_value);
}
inline constexpr auto EmitConvertF32S32 = EmitConvertSigned32ToFloat<IR::Type::F32>;
inline constexpr auto EmitConvertF64S32 = EmitConvertSigned32ToFloat<IR::Type::F64>;
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0);
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
EMIT_NATIVE(CompositeConstructU64, OpCompositeConstruct, U64, uint32_t, uint32_t)
EMIT_NATIVE(CompositeConstructU32x2, OpCompositeConstruct, U32x2, uint32_t, uint32_t)
@@ -120,6 +129,10 @@ inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
EMIT_NATIVE(FPSub32, OpFSub, F32, uint32_t, uint32_t)
EMIT_NATIVE(FPMul32, OpFMul, F32, uint32_t, uint32_t)
EMIT_NATIVE(FPMul64, OpFMul, F64, uint32_t, uint32_t)
inline constexpr auto EmitFPFma64 =
EmitGlsl<GLSLstd450Fma, IR::Type::F64, uint32_t, uint32_t, uint32_t>;
uint32_t EmitFPRecip64(EmitterState& state, uint32_t arg0);
inline constexpr auto EmitFPFma32 =
EmitGlsl<GLSLstd450Fma, IR::Type::F32, uint32_t, uint32_t, uint32_t>;
uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
@@ -271,6 +284,8 @@ inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
inline constexpr auto EmitImageAtomicFMin32 = EmitImage;
inline constexpr auto EmitImageAtomicFMax32 = EmitImage;
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitPhi = EmitUnreachable;
inline constexpr auto EmitTessellationBase = EmitUnreachable;
@@ -68,6 +68,7 @@ struct SpirvRequirements {
bool pixel_valid_mask = false;
bool buffer_int64_atomics = false;
bool coherent_buffers = false;
bool float64 = false;
};
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
@@ -139,6 +140,7 @@ uint32_t TypeU32Pair(EmitterState& state);
uint32_t TypeI32(EmitterState& state);
uint32_t TypeI32Pair(EmitterState& state);
uint32_t TypeF32(EmitterState& state);
uint32_t TypeF64(EmitterState& state);
uint32_t TypeU32Vector(EmitterState& state, uint32_t components);
uint32_t TypeU32Composite(EmitterState& state, uint32_t components);
@@ -167,6 +169,10 @@ template <spv::Op opcode, IR::Type type, typename... Args>
uint32_t EmitNative(EmitterState& state, Args... args) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(opcode, TypeId(state, type), result, args...);
if constexpr (type == IR::Type::F64 &&
(opcode == spv::OpFMul || opcode == spv::OpFDiv || opcode == spv::OpExtInst)) {
state.builder.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction);
}
return result;
}
@@ -47,6 +47,10 @@ uint32_t TypeF32(EmitterState& state) {
return state.builder.Type(spv::OpTypeFloat, 32);
}
uint32_t TypeF64(EmitterState& state) {
return state.builder.Type(spv::OpTypeFloat, 64);
}
uint32_t TypeU32Vector(EmitterState& state, uint32_t components) {
return state.builder.Type(spv::OpTypeVector, TypeU32(state), components);
}
@@ -679,6 +683,19 @@ void DefineModule(EmitterState& state) {
// contract prevents host compilers from treating synthesized IEEE values as finite.
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
32u);
if (state.requirements.float64) {
EXIT_NOT_IMPLEMENTED(state.program.stage != ShaderType::Compute);
EXIT_NOT_IMPLEMENTED(state.input_info.compute->float_mode != 0xc0);
// MODE=0xc0 uses round-to-nearest-even and preserves FP64 input/output denormals.
state.builder.RequireCapability(spv::CapabilityFloat64);
state.builder.RequireCapability(spv::CapabilityRoundingModeRTE);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
64u);
// FP64 denormal preservation is temporarily disabled.
// state.builder.RequireCapability(spv::CapabilityDenormPreserve);
// state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDenormPreserve, 64u);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeRoundingModeRTE, 32u);
}
if (const auto* cs = ShaderWorkgroupInput(state.program.stage, state.input_info)) {
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
@@ -440,6 +440,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
case IR::Type::U64: return TypeU64(state);
case IR::Type::U32x2: return TypeU32Pair(state);
case IR::Type::F32: return TypeF32(state);
case IR::Type::F64: return TypeF64(state);
case IR::Type::U32x3: return TypeU32Vector(state, 3);
case IR::Type::U32x4: return TypeU32Vector(state, 4);
case IR::Type::F32x2: return TypeF32Vector(state, 2);
@@ -198,6 +198,8 @@ constexpr Detail::OpcodeMap MIMG_ATOMIC_OPCODE_LIST[] = {
{0x18u, Opcode::IMAGE_ATOMIC_AND},
{0x19u, Opcode::IMAGE_ATOMIC_OR},
{0x1au, Opcode::IMAGE_ATOMIC_XOR},
{0x1eu, Opcode::IMAGE_ATOMIC_FMIN},
{0x1fu, Opcode::IMAGE_ATOMIC_FMAX},
};
constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCODE_LIST);
@@ -579,6 +579,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::IMAGE_ATOMIC_AND:
case Opcode::IMAGE_ATOMIC_OR:
case Opcode::IMAGE_ATOMIC_XOR:
case Opcode::IMAGE_ATOMIC_FMIN:
case Opcode::IMAGE_ATOMIC_FMAX:
case Opcode::IMAGE_LOAD:
case Opcode::IMAGE_LOAD_MIP:
case Opcode::IMAGE_GET_RESINFO:
@@ -154,6 +154,8 @@ enum class Opcode {
V_CUBEMA_F32,
V_CNDMASK_B32,
V_DOT2C_F32_F16,
V_CVT_F64_I32,
V_CVT_F32_F64,
V_CVT_F32_I32,
V_CVT_F32_U32,
V_CVT_U32_F32,
@@ -173,6 +175,7 @@ enum class Opcode {
V_CVT_F32_UBYTE1,
V_CVT_F32_UBYTE2,
V_CVT_F32_UBYTE3,
V_RCP_F64,
V_RCP_F32,
V_RCP_IFLAG_F32,
V_FRACT_F32,
@@ -234,6 +237,8 @@ enum class Opcode {
V_MAD_I32_I24,
V_MAD_U32_U24,
V_MAD_U64_U32,
V_FMA_F64,
V_MUL_F64,
V_FMA_F32,
V_FMA_F16,
V_PACK_B32_F16,
@@ -571,6 +576,8 @@ enum class Opcode {
IMAGE_ATOMIC_AND,
IMAGE_ATOMIC_OR,
IMAGE_ATOMIC_XOR,
IMAGE_ATOMIC_FMIN,
IMAGE_ATOMIC_FMAX,
IMAGE_SAMPLE,
IMAGE_GATHER4_L,
IMAGE_GATHER4_LZ,
@@ -87,6 +87,9 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = {
{0x00u, Opcode::V_NOP},
{0x01u, Opcode::V_MOV_B32},
{0x02u, Opcode::V_READFIRSTLANE_B32},
{0x04u, Opcode::V_CVT_F64_I32},
{0x0fu, Opcode::V_CVT_F32_F64},
{0x2fu, Opcode::V_RCP_F64},
{0x05u, Opcode::V_CVT_F32_I32},
{0x06u, Opcode::V_CVT_F32_U32},
{0x07u, Opcode::V_CVT_U32_F32},
@@ -146,6 +149,9 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x00u, Opcode::V_NOP},
{0x01u, Opcode::V_MOV_B32},
{0x02u, Opcode::V_READFIRSTLANE_B32},
{0x04u, Opcode::V_CVT_F64_I32},
{0x0fu, Opcode::V_CVT_F32_F64},
{0x2fu, Opcode::V_RCP_F64},
{0x05u, Opcode::V_CVT_F32_I32},
{0x06u, Opcode::V_CVT_F32_U32},
{0x07u, Opcode::V_CVT_U32_F32},
@@ -267,6 +273,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x146u, Opcode::V_CUBETC_F32},
{0x147u, Opcode::V_CUBEMA_F32},
{0x14bu, Opcode::V_FMA_F32},
{0x14cu, Opcode::V_FMA_F64},
{0x165u, Opcode::V_MUL_F64},
{0x148u, Opcode::V_BFE_U32},
{0x149u, Opcode::V_BFE_I32},
{0x14au, Opcode::V_BFI_B32},
@@ -452,6 +460,8 @@ bool IsVopcCompareExec(Opcode opcode);
bool IsVop1FloatSourceOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::V_CVT_F32_F64:
case Opcode::V_RCP_F64:
case Opcode::V_MOV_B32:
case Opcode::V_CVT_F32_F16:
case Opcode::V_CVT_U32_F32:
@@ -709,6 +719,11 @@ void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) {
void DecodeVop1Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
uint32_t opcode, uint32_t vdst, Instruction& inst) {
if (inst.opcode == Opcode::V_CVT_F64_I32 || inst.opcode == Opcode::V_CVT_F32_F64 ||
inst.opcode == Opcode::V_RCP_F64) {
SetUnsupported(inst, Family::VOP1, opcode, "FP64 instructions do not support DPP");
return;
}
const auto modifier = code[word_index + 1u];
const auto src0 = modifier & 0xffu;
SetRawWords(inst, code, word_index, 2);
@@ -758,6 +773,7 @@ bool IsVop2FloatOpcode(Opcode opcode) {
bool IsVop1FloatResultOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::V_CVT_F32_F64:
case Opcode::V_CVT_F32_I32:
case Opcode::V_CVT_F32_U32:
case Opcode::V_CVT_F32_F16:
@@ -1178,6 +1194,7 @@ void DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
uint32_t NativeVop3SourceCount(Opcode opcode) {
switch (opcode) {
case Opcode::V_MUL_F64:
case Opcode::V_MUL_LO_U32:
case Opcode::V_MUL_HI_U32:
case Opcode::V_MUL_LO_I32:
@@ -1328,6 +1345,8 @@ bool SupportsNativeVop3SourceModifiers(Opcode opcode) {
case Opcode::V_MAX_F32:
case Opcode::V_MAC_F32:
case Opcode::V_MAD_F32:
case Opcode::V_MUL_F64:
case Opcode::V_FMA_F64:
case Opcode::V_FMA_F32:
case Opcode::V_PACK_B32_F16:
case Opcode::V_CUBEID_F32:
@@ -223,7 +223,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
const bool store_or_atomic = decoded.opcode == Decoder::Opcode::IMAGE_STORE ||
decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP ||
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_SWAP &&
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR);
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_FMAX);
if (store_or_atomic) {
return index == 0u ? decoded.dst : decoded.src0;
}
@@ -1026,6 +1026,10 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicOr32);
case Decoder::Opcode::IMAGE_ATOMIC_XOR:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicXor32);
case Decoder::Opcode::IMAGE_ATOMIC_FMIN:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMin32);
case Decoder::Opcode::IMAGE_ATOMIC_FMAX:
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMax32);
case Decoder::Opcode::FLAT_LOAD_UBYTE:
case Decoder::Opcode::FLAT_LOAD_SBYTE:
@@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
#include <bit>
#include <unordered_map>
#include <utility>
@@ -225,9 +226,28 @@ IR::Value Translator::ReadOperand(const Decoder::Operand& operand, IR::Type type
}
return ir.INotEqual(ReadRawU32(operand), IR::U32(IR::Value(0u)));
}
if (type == IR::Type::U64) {
const auto pair = ReadU32Pair(operand);
return ir.ConstructU64(pair[0], pair[1]);
if (type == IR::Type::U64 || type == IR::Type::F64) {
auto pair = ReadU32Pair(operand);
if (type == IR::Type::F64) {
if (operand.kind == Decoder::OperandKind::LiteralConstant) {
pair = {IR::U32(IR::Value(0u)), IR::U32(IR::Value(operand.value))};
} else if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
const auto bits = operand.value == 0x3e22f983u
? 0x3fc45f306dc9c882ull
: std::bit_cast<uint64_t>(static_cast<double>(
std::bit_cast<float>(operand.value)));
pair = {IR::U32(IR::Value(static_cast<uint32_t>(bits))),
IR::U32(IR::Value(static_cast<uint32_t>(bits >> 32u)))};
}
if (operand.absolute) {
pair[1] = ir.BitwiseAnd(pair[1], IR::U32(IR::Value(0x7fffffffu)));
}
if (operand.negate) {
pair[1] = ir.BitwiseXor(pair[1], IR::U32(IR::Value(0x80000000u)));
}
}
const auto bits = ir.ConstructU64(pair[0], pair[1]);
return type == IR::Type::F64 ? ir.Emit(IR::ValueOpcode::BitCastF64U64, {bits}) : IR::Value(bits);
}
auto bits = ApplyBitSourceModifiers(operand, ReadRawU32(operand));
if (TypesOverlap(type, IR::Type::F32) && !TypesOverlap(type, IR::Type::U32)) {
@@ -403,6 +423,10 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
Write16Bits(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {bits})));
return;
}
if (type == IR::Type::F64) {
value = ir.Emit(IR::ValueOpcode::BitCastU64F64, {value});
type = IR::Type::U64;
}
if (type == IR::Type::U64) {
WriteU32Pair(operand, {ir.CompositeExtract(value, 0), ir.CompositeExtract(value, 1)});
return;
@@ -842,6 +866,14 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
include_vector(inst.src1);
include_vector(inst.src2);
include_vector(inst.src3);
switch (inst.opcode) {
case Decoder::Opcode::V_CVT_F64_I32: include_vector(inst.dst, 2u); break;
case Decoder::Opcode::V_FMA_F64: include_vector(inst.src2, 2u); [[fallthrough]];
case Decoder::Opcode::V_MUL_F64: include_vector(inst.src1, 2u); [[fallthrough]];
case Decoder::Opcode::V_RCP_F64: include_vector(inst.dst, 2u); [[fallthrough]];
case Decoder::Opcode::V_CVT_F32_F64: include_vector(inst.src0, 2u); break;
default: break;
}
if (inst.family == Decoder::Family::DS) {
switch (inst.opcode) {
case Decoder::Opcode::DS_WRITE_B64:
@@ -297,6 +297,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CVT_F32_UBYTE3: V_CVT_F32_UBYTE(inst, 3); return;
case O::V_CVT_F32_U32: V_CVT_F32_U32(inst); return;
case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return;
case O::V_CVT_F64_I32: return FloatUnary(inst, IR::ValueOpcode::ConvertF64S32);
case O::V_CVT_F32_F64: return FloatUnary(inst, IR::ValueOpcode::ConvertF32F64);
case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return;
case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return;
case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return;
@@ -394,6 +396,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_FREXP_MANT_F32: return V_FREXP_MANT_F32(inst);
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64);
case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32);
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32);
@@ -410,6 +413,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_SUB_F32: return FloatBinary(inst, IR::ValueOpcode::FPSub32, false);
case O::V_SUBREV_F32: return FloatBinary(inst, IR::ValueOpcode::FPSub32, true);
case O::V_MUL_F32: return FloatBinary(inst, IR::ValueOpcode::FPMul32, false);
case O::V_MUL_F64: return FloatBinary(inst, IR::ValueOpcode::FPMul64, false);
case O::V_MIN_F32: return FloatBinary(inst, IR::ValueOpcode::FPMin32, false);
case O::V_MAX_F32: return FloatBinary(inst, IR::ValueOpcode::FPMax32, false);
case O::V_LDEXP_F32: return FloatBinary(inst, IR::ValueOpcode::FPLdexp, false);
@@ -418,6 +422,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MADAK_F32:
case O::V_MAD_F32:
case O::V_FMA_F32: return FloatTernary(inst, IR::ValueOpcode::FPFma32, false, true);
case O::V_FMA_F64: return FloatTernary(inst, IR::ValueOpcode::FPFma64, false, false);
case O::V_MIN3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMinTri32, false, false);
case O::V_MAX3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMaxTri32, false, false);
case O::V_MED3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMedTri32, false, false);
@@ -30,6 +30,7 @@ std::string TypeName(Type type) {
"ImageResource",
"SamplerResource",
"ImageAddress",
"F64",
};
const auto bits = static_cast<uint32_t>(type);
if (bits == 0) {
+1
View File
@@ -27,6 +27,7 @@ enum class Type : uint32_t {
ImageResource = 1u << 17u,
SamplerResource = 1u << 18u,
ImageAddress = 1u << 19u,
F64 = 1u << 20u,
};
constexpr Type operator|(Type lhs, Type rhs) {
@@ -33,6 +33,7 @@ constexpr Type U32 = Type::U32;
constexpr Type U64 = Type::U64;
constexpr Type F16 = Type::F16;
constexpr Type F32 = Type::F32;
constexpr Type F64 = Type::F64;
constexpr Type U32x2 = Type::U32x2;
constexpr Type U32x3 = Type::U32x3;
constexpr Type U32x4 = Type::U32x4;
@@ -220,6 +221,8 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
case ValueOpcode::ImageAtomicAnd32:
case ValueOpcode::ImageAtomicOr32:
case ValueOpcode::ImageAtomicXor32:
case ValueOpcode::ImageAtomicFMin32:
case ValueOpcode::ImageAtomicFMax32:
return {ImageAccess::Atomic, ImageResourceClass::Storage, false};
default: return {};
}
@@ -52,6 +52,8 @@ VALUE_OPCODE(BitCastU16F16, U16, F16)
VALUE_OPCODE(BitCastF16U16, F16, U16)
VALUE_OPCODE(BitCastU32F32, U32, F32)
VALUE_OPCODE(BitCastF32U32, F32, U32)
VALUE_OPCODE(BitCastU64F64, U64, F64)
VALUE_OPCODE(BitCastF64U64, F64, U64)
VALUE_OPCODE(ConvertU16U32, U16, U32)
VALUE_OPCODE(ConvertU32U16, U32, U16)
VALUE_OPCODE(ConvertU8U32, U8, U32)
@@ -61,6 +63,8 @@ VALUE_OPCODE(ConvertF16F32, F16, F32)
VALUE_OPCODE(ConvertS32F32, U32, F32)
VALUE_OPCODE(ConvertU32F32, U32, F32)
VALUE_OPCODE(ConvertF32S32, F32, U32)
VALUE_OPCODE(ConvertF64S32, F64, U32)
VALUE_OPCODE(ConvertF32F64, F32, F64)
VALUE_OPCODE(ConvertF32U32, F32, U32)
VALUE_OPCODE(CompositeConstructU64, U64, U32, U32)
VALUE_OPCODE(CompositeConstructU32x2, U32x2, U32, U32)
@@ -166,6 +170,9 @@ VALUE_OPCODE(FPAdd32, F32, F32, F32)
VALUE_OPCODE(FPSub32, F32, F32, F32)
VALUE_OPCODE(FPFma32, F32, F32, F32, F32)
VALUE_OPCODE(FPMul32, F32, F32, F32)
VALUE_OPCODE(FPMul64, F64, F64, F64)
VALUE_OPCODE(FPFma64, F64, F64, F64, F64)
VALUE_OPCODE(FPRecip64, F64, F64)
VALUE_OPCODE(FPMin32, F32, F32, F32)
VALUE_OPCODE(FPMax32, F32, F32, F32)
VALUE_OPCODE(FPMinTri32, F32, F32, F32, F32)
@@ -284,6 +291,8 @@ VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(GetAttribute, U32, U32, U32)
VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32)
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
@@ -485,7 +485,8 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
image.cube = DescriptorIsCube(descriptor);
const auto format =
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
if (base.atomic && format != Prospero::BufferFormat::k32UInt) {
if (base.atomic && format != Prospero::BufferFormat::k32UInt &&
format != Prospero::BufferFormat::k32Float) {
return SpecializationFail(
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
@@ -514,7 +515,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
fmt::format("storage image descriptor {} uses unsupported format {}", i,
static_cast<uint32_t>(format)));
}
if (raw_sint_storage) {
if (raw_sint_storage || base.atomic) {
image.numeric_class = Prospero::TextureNumericClass::Uint;
}
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
+2 -1
View File
@@ -622,6 +622,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
info.threads_num[2] = regs.cs_regs.num_thread_z;
info.lds_size_dwords = static_cast<uint32_t>(regs.cs_regs.lds_size) * 128u;
info.scratch_size_dwords = data.scratch_size_dwords;
info.float_mode = regs.cs_regs.float_mode;
info.group_id[0] = regs.cs_regs.tgid_x_en != 0;
info.group_id[1] = regs.cs_regs.tgid_y_en != 0;
info.group_id[2] = regs.cs_regs.tgid_z_en != 0;
@@ -727,7 +728,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
void BuildStageStaticKey(const ShaderComputeInputInfo& info, std::vector<uint32_t>& key) {
key.clear();
key.push_back(info.workgroup_register);
key.push_back(info.wave_size);
key.push_back(info.wave_size | (static_cast<uint32_t>(info.float_mode) << 8u));
key.push_back(info.host_subgroup_size);
key.push_back(info.thread_ids_num);
key.push_back(info.lds_size_dwords);
+1
View File
@@ -145,6 +145,7 @@ struct ShaderVertexInputInfo {
};
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
uint8_t float_mode = 0xc0;
uint32_t dispatch_threads_num[3] = {0, 0, 0};
bool group_id[3] = {false, false, false};
bool dispatch_thread_dimensions = false;
+8 -8
View File
@@ -950,9 +950,6 @@ public:
if (deliver_seek_frame || sync_mode != 0) {
return true;
}
if (audio_id) {
return candidate.info.time_stamp <= last_audio_ts;
}
auto now = CurrentTimeNoLock();
return now == 0 || candidate.info.time_stamp <= now;
});
@@ -999,7 +996,9 @@ public:
out->details.audio.size = current_audio->info.details.audio.size;
std::memcpy(out->details.audio.language_code,
current_audio->info.details.audio.language_code, 4);
last_audio_ts = out->time_stamp;
start_time_ms = out->time_stamp;
clock_start = std::chrono::steady_clock::now();
paused_extra = {};
RecordLoopBoundary(*current_audio);
return true;
}
@@ -1064,7 +1063,6 @@ private:
video_done = true;
audio_done = true;
seek_video_frame_pending = false;
last_audio_ts = 0;
last_output_loop_offset = 0;
pending_loop_warnings = 0;
}
@@ -1541,11 +1539,14 @@ private:
return false;
}
auto* dst = buffer->Get();
std::memset(dst, 0, static_cast<size_t>(size));
auto* c = dst + pitch * h;
// PPSA02433 samples the padded columns; zero chroma would turn them green.
std::memset(dst, s->codecpar->color_range == AVCOL_RANGE_JPEG ? 0 : 16,
static_cast<size_t>(pitch) * h);
std::memset(c, 128, static_cast<size_t>(pitch) * h / 2);
for (int y = 0; y < src->height; y++) {
std::memcpy(dst + y * pitch, nv12->data[0] + y * nv12->linesize[0], src->width);
}
auto* c = dst + pitch * h;
for (int y = 0; y < src->height / 2; y++) {
std::memcpy(c + y * pitch, nv12->data[1] + y * nv12->linesize[1], src->width);
}
@@ -1674,7 +1675,6 @@ private:
int32_t trick_speed = AVPLAYER_TRICK_SPEED_NORMAL;
uint32_t sync_mode = 0;
uint64_t start_time_ms = 0;
uint64_t last_audio_ts = 0;
uint64_t last_output_loop_offset = 0;
uint32_t pending_loop_warnings = 0;
std::chrono::steady_clock::time_point clock_start {};
+285
View File
@@ -15837,6 +15837,7 @@ private:
device_features.shaderImageGatherExtended = true;
device_features.sampleRateShading = true;
device_features.shaderInt64 = true;
device_features.shaderFloat64 = available_features.shaderFloat64;
device_features.fillModeNonSolid = true;
device_features.tessellationShader = true;
device_info.pEnabledFeatures = &device_features;
@@ -16400,6 +16401,11 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::V_MAX3_F32:
case Opcode::V_MED3_F32:
case Opcode::V_DOT2C_F32_F16:
case Opcode::V_CVT_F64_I32:
case Opcode::V_CVT_F32_F64:
case Opcode::V_RCP_F64:
case Opcode::V_MUL_F64:
case Opcode::V_FMA_F64:
case Opcode::V_CVT_F32_I32:
case Opcode::V_CVT_F32_U32:
case Opcode::V_CVT_U32_F32:
@@ -16634,6 +16640,8 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
case Opcode::IMAGE_ATOMIC_AND:
case Opcode::IMAGE_ATOMIC_OR:
case Opcode::IMAGE_ATOMIC_XOR:
case Opcode::IMAGE_ATOMIC_FMIN:
case Opcode::IMAGE_ATOMIC_FMAX:
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
@@ -21687,6 +21695,157 @@ TestCase VectorSpecialF32FlushesDenormalInputs() {
O::V_SQRT_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase VectorF64CapturedScreenSpaceShadows() {
using O = ShaderOpcode;
TestCase test;
test.name = "VectorF64CapturedScreenSpaceShadows";
const auto store_pair = [&](u32 reg, double value) {
const auto bits = std::bit_cast<uint64_t>(value);
AppendStoreVgpr(&test.code, reg, static_cast<u32>(test.expected.size()));
AppendStoreVgpr(&test.code, reg + 1,
static_cast<u32>(test.expected.size() + 1));
test.expected.insert(test.expected.end(), {static_cast<u32>(bits),
static_cast<u32>(bits >> 32)});
};
for (const auto values :
{std::array<int32_t, 3>{63, 127, 101},
std::array<int32_t, 3>{16777217, -16777217, 123456789},
std::array<int32_t, 3>{INT32_MIN, INT32_MAX, -31}}) {
AppendVMovLiteral(&test.code, 7, static_cast<u32>(values[0]));
AppendVMovLiteral(&test.code, 1, static_cast<u32>(values[1]));
AppendVMovLiteral(&test.code, 9, static_cast<u32>(values[2]));
// Exact FP64 instruction words from b90e2024732c6111, PCs 0xc8 through
// 0x130.
test.code.insert(test.code.end(), {0x7e060907u, 0x7e0a0901u, 0x7e020909u});
store_pair(3, double(values[0]));
store_pair(5, double(values[1]));
store_pair(1, double(values[2]));
test.code.push_back(0x7e065f03u);
const double reciprocal = 1.0 / double(values[0]);
store_pair(3, reciprocal);
test.code.insert(test.code.end(), {0xd5650005u, 0x00020305u});
const double product = double(values[1]) * double(values[2]);
store_pair(5, product);
test.code.insert(test.code.end(), {0xd54c0001u, 0x84060705u});
const double result = std::fma(product, reciprocal, -double(values[2]));
store_pair(1, result);
test.code.push_back(0x7e041f01u);
AppendStoreVgpr(&test.code, 2, static_cast<u32>(test.expected.size()));
test.expected.push_back(std::bit_cast<u32>(static_cast<float>(result)));
}
AppendEnd(&test.code);
test.opcodes = {O::V_MOV_B32, O::V_CVT_F64_I32, O::V_RCP_F64,
O::V_MUL_F64, O::V_FMA_F64, O::V_CVT_F32_F64,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpCapability Float64",
"OpTypeFloat 64",
"OpFDiv",
"Fma",
"SignedZeroInfNanPreserve 64",
"RoundingModeRTE 32"};
test.ir_counts = {{"ConvertF64S32", 9},
{"FPRecip64", 3},
{"FPMul64", 3},
{"FPFma64", 3},
{"ConvertF32F64", 3}};
ShaderComputeInputInfo info{};
std::vector<u32> normal_key, other_key;
BuildStageStaticKey(info, normal_key);
info.float_mode = 0xc4;
BuildStageStaticKey(info, other_key);
Require(test.name, "pipeline identity",
normal_key != other_key && normal_key.size() == other_key.size(),
"FP64 rounding modes must not share a compiled shader key");
return test;
}
TestCase VectorF64ModesModifiersAndExec() {
using O = ShaderOpcode;
TestCase test;
test.name = "VectorF64ModesModifiersAndExec";
const auto load_pair = [&](u32 reg, uint64_t bits) {
AppendVMovLiteral(&test.code, reg, static_cast<u32>(bits));
AppendVMovLiteral(&test.code, reg + 1, static_cast<u32>(bits >> 32));
};
const auto store_pair = [&](u32 reg, uint64_t bits) {
AppendStoreVgpr(&test.code, reg, static_cast<u32>(test.expected.size()));
AppendStoreVgpr(&test.code, reg + 1,
static_cast<u32>(test.expected.size() + 1));
test.expected.insert(test.expected.end(), {static_cast<u32>(bits),
static_cast<u32>(bits >> 32)});
};
const auto store_word = [&](u32 reg, u32 bits) {
AppendStoreVgpr(&test.code, reg, static_cast<u32>(test.expected.size()));
test.expected.push_back(bits);
};
for (const auto [source, expected] :
{std::pair<uint64_t, uint64_t>{0, 0x7ff0000000000000ull},
{0x8000000000000000ull, 0xfff0000000000000ull},
{0x7ff0000000000000ull, 0},
{0xfff0000000000000ull, 0x8000000000000000ull},
{0x0008000000000000ull, 0x7fe0000000000000ull},
{0x7fe0000000000000ull, 0x0008000000000000ull}}) {
load_pair(1, source);
test.code.push_back(EncodeVop1(0x2f, 1, Vgpr(1)));
store_pair(1, expected);
}
// A fused result retains the residual that separate multiply/add would round
// to zero.
load_pair(1, std::bit_cast<uint64_t>(1.0 + 0x1p-27));
load_pair(3, std::bit_cast<uint64_t>(1.0 - 0x1p-27));
AppendVop3(&test.code, 0x14c, 1, Vgpr(1), Vgpr(3), 243u);
store_pair(1, std::bit_cast<uint64_t>(-0x1p-54));
// Unaligned pairs, SGPR pairs, inline floats, literal highword expansion, ABS
// then NEG.
test.code.push_back(EncodeSop1(0x03, 8, 255));
test.code.push_back(0u);
test.code.push_back(EncodeSop1(0x03, 9, 255));
test.code.push_back(0xbff00000u);
AppendVop3(&test.code, 0x165, 1, 8u, 244u, 0, 1, 0, false, 0, 1);
store_pair(1, std::bit_cast<uint64_t>(-2.0));
AppendVop3(&test.code, 0x165, 1, 255u, 242u);
test.code.push_back(0x40080000u);
store_pair(1, std::bit_cast<uint64_t>(3.0));
AppendVop3(&test.code, 0x165, 1, 248u, 242u);
store_pair(1, 0x3fc45f306dc9c882ull);
AppendVop3(&test.code, 0x184, 1, InlineU32(3), 0);
store_pair(1, std::bit_cast<uint64_t>(3.0));
AppendVop3(&test.code, 0x1af, 1, 244u, 0);
store_pair(1, std::bit_cast<uint64_t>(0.5));
// Preserve both destination words when EXEC is clear, restoring the original
// live lanes.
test.code.push_back(EncodeSop1(0x04, 12, 126));
test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
test.code.push_back(EncodeVop1(0x04, 1, InlineU32(9)));
test.code.push_back(EncodeSop1(0x04, 126, 12));
store_pair(1, std::bit_cast<uint64_t>(0.5));
for (const auto [source, expected] :
{std::pair<uint64_t, u32>{0x47f0000000000000ull, 0x7f7fffffu},
{0xc7f0000000000000ull, 0xff7fffffu},
{0x7ff0000000000000ull, 0x7f800000u},
{0xfff0000000000000ull, 0xff800000u},
{0x3800000000000000ull, 0},
{0xb800000000000000ull, 0x80000000u},
{0x3810000000000000ull, 0x00800000u},
{0x3ff0000010000000ull, 0x3f800000u}}) {
load_pair(1, source);
AppendVop3(&test.code, 0x18f, 1, Vgpr(1), 0);
store_word(1, expected);
}
load_pair(1, 0x7ff8123456789abcull);
test.code.push_back(EncodeVop1(0x2f, 1, Vgpr(1)));
AppendVMovLiteral(&test.code, 3, 0x7ff80000u);
test.code.push_back(EncodeVop2(0x1b, 2, Vgpr(3), 2));
store_word(2, 0x7ff80000u);
AppendEnd(&test.code);
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64,
O::V_CVT_F64_I32, O::V_RCP_F64, O::V_MUL_F64,
O::V_FMA_F64, O::V_CVT_F32_F64, O::V_AND_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
TestCase VectorRcpIflagF32IntegerReciprocal() {
using O = ShaderOpcode;
@@ -28139,6 +28298,124 @@ TestCase ImageAtomicVariants() {
return test;
}
template <bool max_value> TestCase ImageAtomicFloatGlcAndExec() {
using O = ShaderOpcode;
std::vector<u32> code;
constexpr u32 atomic_word = max_value ? 0xf07c0108u : 0xf0780108u;
code.push_back(EncodeSop1(0x04, 12, 126)); // Preserve active workgroup lanes.
AppendVMovU32(&code, 8, 2);
AppendVMovU32(&code, 9, 1);
AppendVMovLiteral(&code, 5, 0x40000000u); // 2.0
// FMAX uses the captured GLC=0 instruction word.
code.insert(code.end(), {atomic_word, 0x00010508u});
AppendStoreVgpr(&code, 5, 0);
AppendVMovLiteral(&code, 5, max_value ? 0x40800000u : 0x3f800000u);
code.insert(code.end(), {atomic_word | 0x2000u, 0x00010508u}); // GLC=1.
AppendStoreVgpr(&code, 5, 1);
AppendVMovLiteral(&code, 5,
max_value ? 0x41000000u : 0x3f000000u); // Inactive lane.
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
code.insert(code.end(), {atomic_word | 0x2000u, 0x00010508u});
code.push_back(EncodeSop1(0x04, 126, 12));
AppendStoreVgpr(&code, 5, 2);
AppendEnd(&code);
TestCase test;
test.name = max_value ? "ImageAtomicFMaxCapturedGlcAndExec"
: "ImageAtomicFMinGlcAndExec";
test.code = std::move(code);
test.expected = {0x40000000u, 0x40000000u,
max_value ? 0x41000000u : 0x3f000000u};
test.opcodes = {O::V_MOV_B32, O::S_MOV_B64,
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
const std::array<u32, 8> descriptor = {
0x504a7c00u, 0xc1600000u, 0x0086c0efu, 0x91b80924u,
0x00000000u, 0x00700080u, 0x00000000u, 0x00000000u};
std::copy_n(descriptor.begin(), 8, test.user_data.begin() + 4);
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.storage_image_r32ui[6] = max_value ? 0x3f800000u : 0x40800000u;
test.expected_storage_image_r32ui = test.storage_image_r32ui;
test.expected_storage_image_r32ui[6] = max_value ? 0x40800000u : 0x3f800000u;
test.required_spirv = {"OpImageTexelPointer", "OpAtomicCompareExchange", "R32ui"};
return test;
}
template <bool max_value> TestCase ImageAtomicFloatSpecialValues() {
using O = ShaderOpcode;
// source, previous texel, expected minimum, expected maximum.
const u32 cases[][4] = {
{0x40000000u, 0x3f800000u, 0x3f800000u, 0x40000000u},
{0xc0000000u, 0xbf800000u, 0xc0000000u, 0xbf800000u},
{0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u},
{0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u},
{0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u},
{0x7fcabcdeu, 0x3f800000u, 0x3f800000u, 0x3f800000u},
{0x3f800000u, 0x7fa54321u, 0x7fa54321u, 0x7fa54321u},
{0x7faabcdeu, 0x3f800000u, 0x3f800000u, 0x3f800000u},
{0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u},
{0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u},
{0x80000000u, 0x80000001u, 0x80000001u, 0x80000000u},
{0x80000001u, 0x80000000u, 0x80000001u, 0x80000000u},
{0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u},
{0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u},
};
TestCase test;
test.name = max_value ? "ImageAtomicFMaxSpecialValues"
: "ImageAtomicFMinSpecialValues";
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.expected_storage_image_r32ui = test.storage_image_r32ui;
for (u32 i = 0; i < std::size(cases); i++) {
AppendVMovU32(&test.code, 20, i & 3u);
AppendVMovU32(&test.code, 21, i >> 2u);
AppendVMovLiteral(&test.code, 0, cases[i][0]);
test.code.push_back(EncodeMimg0(max_value ? 0x1f : 0x1e, 1, 0, true));
test.code.push_back(EncodeMimg1(0, 20));
AppendStoreVgpr(&test.code, 0, i);
test.expected.push_back(cases[i][1]);
test.storage_image_r32ui[i] = cases[i][1];
test.expected_storage_image_r32ui[i] = cases[i][max_value ? 3 : 2];
}
AppendEnd(&test.code);
test.opcodes = {O::V_MOV_B32,
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
template <bool max_value> TestCase ImageAtomicFloatContendedWorkgroup() {
using O = ShaderOpcode;
TestCase test;
test.name = max_value ? "ImageAtomicFMaxContendedWorkgroup"
: "ImageAtomicFMinContendedWorkgroup";
test.code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // Float thread_id.x.
AppendVMovU32(&test.code, 20, 0);
AppendVMovU32(&test.code, 21, 0);
test.code.push_back(EncodeMimg0(max_value ? 0x1f : 0x1e, 1));
test.code.push_back(EncodeMimg1(1, 20));
AppendEnd(&test.code);
test.opcodes = {O::V_CVT_F32_U32, O::V_MOV_B32,
max_value ? O::IMAGE_ATOMIC_FMAX : O::IMAGE_ATOMIC_FMIN,
O::S_ENDPGM};
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
test.has_user_data = true;
test.storage_image_r32ui = std::vector<u32>(16, 0);
test.storage_image_r32ui[0] =
max_value ? 0xc2c80000u : 0x42c80000u; // -/+100.0
test.expected_storage_image_r32ui = test.storage_image_r32ui;
test.expected_storage_image_r32ui[0] =
max_value ? 0x427c0000u : 0u; // 63.0 / 0.0
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase ImageAtomicGlc0DoesNotReturnOldValue() {
using O = ShaderOpcode;
@@ -28754,6 +29031,8 @@ std::vector<TestCase> MakeCases() {
AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange);
AddCase(VectorSpecialF32FlushesDenormalInputs);
AddCase(VectorRcpIflagF32IntegerReciprocal);
AddCase(VectorF64CapturedScreenSpaceShadows);
AddCase(VectorF64ModesModifiersAndExec);
AddCase(VectorSinCosMaxFiniteSpecialCases);
AddCase(VectorCompareOps);
AddCase(VectorVop3CompareEqI64OnGpu);
@@ -28951,6 +29230,12 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageAtomicSwapReturnsPreviousTexel);
AddCase(ImageStoreAndAtomicUseSeparateBindings);
AddCase(ImageAtomicVariants);
AddCase(ImageAtomicFloatGlcAndExec<true>);
AddCase(ImageAtomicFloatGlcAndExec<false>);
AddCase(ImageAtomicFloatSpecialValues<true>);
AddCase(ImageAtomicFloatSpecialValues<false>);
AddCase(ImageAtomicFloatContendedWorkgroup<true>);
AddCase(ImageAtomicFloatContendedWorkgroup<false>);
AddCase(ImageAtomicGlc0DoesNotReturnOldValue);
AddCase(MultipleWorkitemsGlobalId);
AddCase(DispatcherIrreducibleControlFlow);