diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 8a9fe9cf3..5fcd9f21d 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -337,6 +337,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa check_feature(device_features2.features.shaderImageGatherExtended, "shaderImageGatherExtended"); + check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod"); check_feature(device_features2.features.independentBlend, "independentBlend"); check_feature(device_features2.features.tessellationShader, "tessellationShader"); @@ -636,6 +637,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, device_features.shaderStorageImageMultisample = supported_features2.features.shaderStorageImageMultisample; device_features.shaderImageGatherExtended = VK_TRUE; + device_features.shaderResourceMinLod = VK_TRUE; device_features.independentBlend = VK_TRUE; device_features.dualSrcBlend = VK_TRUE; device_features.tessellationShader = VK_TRUE; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index a4d895b8b..17ec78357 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -77,6 +77,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) { layout.coord = cursor; cursor += info.coordinate_components; if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++; + if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++; return layout; } @@ -1069,7 +1070,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { dref_value = AddressF32(ctx, mem, *address, layout.dref); } } - std::array operands {}; + // _cl clamps the computed LOD from below. + uint32_t min_lod = 0; + if (layout.clamp != NoImageComponent) { + min_lod = AddressF32(ctx, mem, *address, layout.clamp); + } + std::array operands {}; size_t operand_count = 0; if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) { operands[0] = spv::ImageOperandsGradMask; @@ -1081,6 +1087,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) { lod = AddressF32(ctx, mem, *address, layout.lod); } + // SPIR-V takes MinLod only with implicit LOD or Grad. + if (min_lod != 0) { + lod = EmitGlsl(state, lod, min_lod); + min_lod = 0; + } operands[0] = spv::ImageOperandsLodMask; operands[1] = lod; operand_count = 2; @@ -1104,6 +1115,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { sample_operands[sample_operand_count++] = PackedOffset( ctx, offset_value, candidate.dimension, sample_operands[0]); } + // MinLod follows the offset in operand order. + if (min_lod != 0) { + state.builder.RequireCapability(spv::CapabilityMinLod); + sample_operand_count = std::max(size_t{1}, sample_operand_count); + sample_operands[0] |= spv::ImageOperandsMinLodMask; + sample_operands[sample_operand_count++] = min_lod; + } const auto coord = CoordF32(ctx, mem, *address, layout.coord, ImageDimensionInfoFor(candidate.dimension).coordinate_components, diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index fdf9a5404..99c14e255 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -283,6 +283,7 @@ struct ImageSampleLayout { uint32_t bias = NoImageComponent; uint32_t coord = 0; uint32_t lod = NoImageComponent; + uint32_t clamp = NoImageComponent; uint32_t grad_x = NoImageComponent; uint32_t grad_y = NoImageComponent; }; diff --git a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp index c66836212..4469a6bcd 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp @@ -73,7 +73,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d if ((flags & ImageSampleFlagBias) != 0) { components++; } - if ((flags & ImageSampleFlagLod) != 0) { + if ((flags & (ImageSampleFlagLod | ImageSampleFlagLodClamp)) != 0) { components++; } if ((flags & ImageSampleFlagDerivative) != 0) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c30356c3b..4cde9df49 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -21559,8 +21559,9 @@ private: "shaderStorageImageWriteWithoutFormat is not supported"); Require("VulkanHarness", "dispatch", available_features.shaderImageGatherExtended == true && + available_features.shaderResourceMinLod == true && available_derivatives.computeDerivativeGroupQuads == true, - "image gather or compute derivative quads are not supported"); + "image gather, MinLod or compute derivative quads are not supported"); Require("VulkanHarness", "dispatch", available_features11.storageBuffer16BitAccess == true && available_features12.storageBuffer8BitAccess == true, @@ -21694,6 +21695,7 @@ private: device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderStorageImageMultisample = available_features.shaderStorageImageMultisample; device_features.shaderImageGatherExtended = true; + device_features.shaderResourceMinLod = true; device_features.sampleRateShading = true; device_features.shaderInt64 = true; device_features.shaderFloat64 = available_features.shaderFloat64; @@ -39224,6 +39226,48 @@ TestCase ImageGatherExplicitLod() { return test; } +TestCase ImageSampleLodClamp() { + using O = ShaderOpcode; + + // _cl clamps the computed LOD from below: compute's implicit LOD and zero + // derivatives would both select mip 0, so a clamp of 1 must read mip 1. + std::vector code; + AppendVMovU32(&code, 20, 0); + AppendVMovU32(&code, 21, 0); + AppendVMovU32(&code, 22, 0); + AppendVMovU32(&code, 23, 0); + AppendVMovLiteral(&code, 24, 0x3f000000u); + AppendVMovLiteral(&code, 25, 0x3f000000u); + AppendVMovLiteral(&code, 26, 0x3f800000u); + code.push_back(EncodeMimg0(0x21, 0x1)); + code.push_back(EncodeMimg1(0, 24, 0, 2)); + code.push_back(EncodeMimg0(0x23, 0x1)); + code.push_back(EncodeMimg1(1, 20, 0, 2)); + AppendStoreVgpr(&code, 0, 0); + AppendStoreVgpr(&code, 1, 1); + AppendEnd(&code); + + TestCase test; + test.name = "ImageSampleLodClamp"; + test.code = std::move(code); + test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.expected = std::vector(2, 0x41000000u); + test.sampled_image_rgba_mips = {std::vector(4 * 4 * 4, 0x3f800000u), + std::vector(2 * 2 * 4, 0x41000000u)}; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float); + test.user_data[1] |= 3u << 30u; + test.user_data[2] = 3u << 14u; + test.user_data[3] |= 1u << 16u; + test.user_data[8] = 0x92u; + test.user_data[9] = 0xfff000u; + test.user_data[10] = 0x05000000u; + test.user_data[50] = test.expected.size() * sizeof(u32); + test.user_data[51] = 3u << 28u; + test.has_user_data = true; + test.required_spirv = {"MinLod"}; + return test; +} + template TestCase ImageGatherLodPerLane() { using O = ShaderOpcode; constexpr std::array names{ @@ -44788,6 +44832,7 @@ std::vector MakeCases() { AddCase(ImageSampleAndGather); AddCase(ImageCubeGradientsPreserveDerivatives); AddCase(ImageGatherExplicitLod); + AddCase(ImageSampleLodClamp); AddCase(ImageGatherLodPerLane<0>); AddCase(ImageGatherLodPerLane<1>); AddCase(ImageGatherLodPerLane<2>); @@ -51205,6 +51250,7 @@ int main(int argc, char **argv) { VulkanHarness vulkan; vulkan.CheckComparisonDepthTexture(); RunCase(&vulkan, ImageGatherExplicitLod()); + RunCase(&vulkan, ImageSampleLodClamp()); RunCase(&vulkan, ImageGatherLodPerLane<0>()); RunCase(&vulkan, ImageGatherLodPerLane<1>()); RunCase(&vulkan, ImageGatherLodPerLane<2>());