shader: fix filtering for scaled 8-bit textures
Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
This commit is contained in:
parent
deb45a8bb2
commit
96c067d0fb
|
|
@ -20,6 +20,7 @@ struct FormatInfo {
|
|||
|
||||
constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k8UNorm, 1, 0, 1, true, false},
|
||||
{BufferFormat::k8UScaled, 1, 0, 0, true, false},
|
||||
{BufferFormat::k8SNorm, 0, 0, 1, false, false},
|
||||
{BufferFormat::k8UInt, 1, 0, 1, true, true},
|
||||
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
|
||||
|
|
@ -28,6 +29,7 @@ constexpr FormatInfo kFormatInfo[] = {
|
|||
{BufferFormat::k16SInt, 2, 0, 2, true, false, true},
|
||||
{BufferFormat::k16Float, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8UScaled, 2, 0, 0, true, false},
|
||||
{BufferFormat::k8_8SNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8UInt, 2, 0, 2, true, true},
|
||||
{BufferFormat::k8_8SInt, 2, 0, 2, true, false, true},
|
||||
|
|
@ -231,7 +233,12 @@ TextureNumericClass SampledTextureNumericClass(BufferFormat format) {
|
|||
}
|
||||
|
||||
BufferFormat RemapTextureFormat(BufferFormat format) {
|
||||
return format == BufferFormat::k11_11_10UInt ? BufferFormat::k32UInt : format;
|
||||
switch (format) {
|
||||
case BufferFormat::k8UScaled: return BufferFormat::k8UNorm;
|
||||
case BufferFormat::k8_8UScaled: return BufferFormat::k8_8UNorm;
|
||||
case BufferFormat::k11_11_10UInt: return BufferFormat::k32UInt;
|
||||
default: return format;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::Prospero
|
||||
|
|
|
|||
|
|
@ -391,28 +391,50 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
|||
const auto format = state.program.info.images[mem.resource].conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
EXIT_IF(Prospero::SampledTextureNumericClass(format) != Prospero::TextureNumericClass::Uint ||
|
||||
Prospero::RemapTextureFormat(format) == format ||
|
||||
info.type != Format::ComponentType::Uint || !info.packed_bitfield ||
|
||||
info.byte_size != sizeof(uint32_t) || info.component_count == 0u ||
|
||||
EXIT_IF(Prospero::RemapTextureFormat(format) == format || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
EXIT_IF(info.type != Format::ComponentType::Uscaled &&
|
||||
(info.type != Format::ComponentType::Uint || !info.packed_bitfield ||
|
||||
info.byte_size != sizeof(uint32_t)));
|
||||
return info;
|
||||
}
|
||||
|
||||
uint32_t ImageGatherSource(const EmitterState& state, const IR::MemoryInfo& mem) {
|
||||
const auto component = ImageGatherComponent(mem.dmask);
|
||||
const auto info = ImageConversionFormat(state, mem);
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return component;
|
||||
if (info.packed_bitfield) return 0u;
|
||||
const auto selector =
|
||||
(state.program.info.images[mem.resource].shader_swizzle >> (component * 3u)) & 7u;
|
||||
return selector >= 4u ? (selector - 4u) % info.component_count : 0u;
|
||||
}
|
||||
|
||||
uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
|
||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
|
||||
|
||||
const auto packed = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, texel, 0u);
|
||||
uint32_t components[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
|
||||
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto scalar_type = ImageScalarType(ctx.state, numeric_class);
|
||||
uint32_t packed = 0;
|
||||
if (info.packed_bitfield) {
|
||||
packed = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type, packed, texel, 0u);
|
||||
}
|
||||
uint32_t components[4] {};
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
components[component] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state),
|
||||
components[component], packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, info.component_bits[component]));
|
||||
if (info.packed_bitfield) {
|
||||
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, scalar_type,
|
||||
components[component], packed,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, info.component_bits[component]));
|
||||
} else {
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 255.0f));
|
||||
}
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
components[component] = components[component % info.component_count];
|
||||
|
|
@ -423,16 +445,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
|||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = ConstantU32(ctx.state, 1u);
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 1u)
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
selected[component] = ConstantU32(ctx.state, 0u);
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u)
|
||||
: ZeroF32(ctx.state);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
selected[0], selected[1], selected[2], selected[3]);
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -443,10 +468,20 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
|
|||
const auto component = ImageGatherComponent(mem.dmask);
|
||||
const auto selector =
|
||||
(ctx.state.program.info.images[mem.resource].shader_swizzle >> (component * 3u)) & 7u;
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto vector_type = ImageVectorType(ctx.state, numeric_class, 4);
|
||||
if (selector < 4u) {
|
||||
const auto value = ConstantU32(ctx.state, selector == 1u ? 1u : 0u);
|
||||
return ctx.state.builder.Constant(spv::OpConstantComposite, TypeU32Vector(ctx.state, 4),
|
||||
value, value, value, value);
|
||||
const auto value = info.packed_bitfield
|
||||
? ConstantU32(ctx.state, selector == 1u ? 1u : 0u)
|
||||
: ConstantF32Value(ctx.state, selector == 1u ? 1.0f : 0.0f);
|
||||
return ctx.state.builder.Constant(spv::OpConstantComposite, vector_type, value, value,
|
||||
value, value);
|
||||
}
|
||||
if (!info.packed_bitfield) {
|
||||
const auto scale = ConstantF32Value(ctx.state, 255.0f);
|
||||
const auto scales = ctx.state.builder.Constant(spv::OpConstantComposite, vector_type,
|
||||
scale, scale, scale, scale);
|
||||
return Binary(ctx.state, spv::OpFMul, vector_type, gathered, scales);
|
||||
}
|
||||
|
||||
uint32_t values[4] {};
|
||||
|
|
@ -488,10 +523,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
|||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto vector_type = ImageVectorType(state, numeric_class, 4);
|
||||
const auto scalar_type = ImageScalarType(state, numeric_class);
|
||||
const auto component =
|
||||
ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask)
|
||||
: 0u;
|
||||
const auto component = ImageGatherSource(state, mem);
|
||||
uint32_t values[2] {};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
const auto sample_coord =
|
||||
|
|
@ -515,6 +547,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
|||
uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
|
||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
|
||||
EXIT_IF(info.type != Format::ComponentType::Uint || !info.packed_bitfield);
|
||||
|
||||
auto packed = ConstantU32(ctx.state, 0u);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
|
|
@ -669,7 +702,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
Prospero::BufferFormat::kInvalid) {
|
||||
ctx.Fail(inst, "uses depth comparison with a packed integer image");
|
||||
ctx.Fail(inst, "uses depth comparison with a converted image");
|
||||
return;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
|
|
@ -700,9 +733,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||
? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state);
|
||||
} else {
|
||||
component_or_dref = ConstantU32(
|
||||
state, ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask) : 0u);
|
||||
component_or_dref = ConstantU32(state, ImageGatherSource(state, mem));
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
uint32_t offset = 0;
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ enum class SamplerClass : uint8_t { Float, Integer, PointInteger };
|
|||
|
||||
SamplerClass ClassifySampler(const ImageResource& image) {
|
||||
if (image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
(image.numeric_class == Prospero::TextureNumericClass::Uint &&
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid)) {
|
||||
return SamplerClass::PointInteger;
|
||||
}
|
||||
return image.numeric_class == Prospero::TextureNumericClass::Uint ? SamplerClass::Integer
|
||||
|
|
|
|||
|
|
@ -1283,6 +1283,7 @@ struct TestCase {
|
|||
std::vector<u32> sampled_image_rgba;
|
||||
std::vector<std::vector<u32>> sampled_image_rgba_mips;
|
||||
vk::Format sampled_image_format = vk::Format::eR32G32B32A32Sfloat;
|
||||
vk::Filter sampler_filter = vk::Filter::eNearest;
|
||||
u32 sampled_image_dwords_per_pixel = 4;
|
||||
vk::ImageType sampled_image_type = vk::ImageType::e2D;
|
||||
vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D;
|
||||
|
|
@ -1316,6 +1317,7 @@ struct TestCase {
|
|||
std::vector<BdaMapping> bda_mappings;
|
||||
bool expand_shader_data_storage = false;
|
||||
bool expected_force_point_sampler = false;
|
||||
float expected_float_tolerance = 0.0f;
|
||||
std::vector<u32> gds_initial;
|
||||
std::vector<u32> expected_gds;
|
||||
std::vector<std::pair<std::string, size_t>> decoded_counts;
|
||||
|
|
@ -12981,7 +12983,7 @@ public:
|
|||
copied_depth_sample.layout = copy_sample_image.backing.state.layout;
|
||||
scheduler.Finish();
|
||||
auto copy_sample_output = CreateStorageBuffer(name, {}, 2);
|
||||
const auto copy_sampler = CreateNearestSampler(name);
|
||||
const auto copy_sampler = CreateSampler(name);
|
||||
Dispatch(copy_sample_test, copy_sample_program, copy_sample_output,
|
||||
nullptr, &copied_depth_sample, nullptr, nullptr, copy_sampler);
|
||||
Require(
|
||||
|
|
@ -14326,11 +14328,12 @@ public:
|
|||
return ret;
|
||||
}
|
||||
|
||||
vk::Sampler CreateNearestSampler(const char *shader_name, u32 mip_levels = 1) {
|
||||
vk::Sampler CreateSampler(const char *shader_name, u32 mip_levels = 1,
|
||||
vk::Filter filter = vk::Filter::eNearest) {
|
||||
vk::SamplerCreateInfo sampler_info{};
|
||||
sampler_info.sType = vk::StructureType::eSamplerCreateInfo;
|
||||
sampler_info.magFilter = vk::Filter::eNearest;
|
||||
sampler_info.minFilter = vk::Filter::eNearest;
|
||||
sampler_info.magFilter = filter;
|
||||
sampler_info.minFilter = filter;
|
||||
sampler_info.mipmapMode = vk::SamplerMipmapMode::eNearest;
|
||||
sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge;
|
||||
|
|
@ -17688,6 +17691,15 @@ void CompareWords(const TestCase &test, const char *stage,
|
|||
if (actual == expected) {
|
||||
return;
|
||||
}
|
||||
if (test.expected_float_tolerance > 0.0f &&
|
||||
std::equal(expected.begin(), expected.end(), actual.begin(), actual.end(),
|
||||
[&](u32 expected_word, u32 actual_word) {
|
||||
return std::abs(std::bit_cast<float>(expected_word) -
|
||||
std::bit_cast<float>(actual_word)) <=
|
||||
test.expected_float_tolerance;
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
std::ostringstream out;
|
||||
out << "expected [";
|
||||
for (size_t i = 0; i < expected.size(); i++) {
|
||||
|
|
@ -17735,6 +17747,12 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
|||
compiled.program.info.samplers[0].force_point_filtering,
|
||||
"bit-packed sampled image did not force point filtering");
|
||||
}
|
||||
if (test.sampler_filter == vk::Filter::eLinear) {
|
||||
Require(test.name, "sampler specialization",
|
||||
std::ranges::none_of(compiled.program.info.samplers,
|
||||
[](const auto &sampler) { return sampler.force_point_filtering; }),
|
||||
"filterable sampled image incorrectly forced point filtering");
|
||||
}
|
||||
if (test.compile_only) {
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
return;
|
||||
|
|
@ -17809,8 +17827,9 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
|||
atomic64 ? vk::Format::eR64Uint : vk::Format::eR32Uint);
|
||||
}
|
||||
if (needs_sampler) {
|
||||
sampler = vulkan->CreateNearestSampler(
|
||||
test.name, sampled_image.mip_levels - test.sampled_image_view_base_mip);
|
||||
sampler = vulkan->CreateSampler(
|
||||
test.name, sampled_image.mip_levels - test.sampled_image_view_base_mip,
|
||||
test.sampler_filter);
|
||||
}
|
||||
|
||||
vulkan->Dispatch(test, compiled, buffer, needs_gds ? &gds_buffer : nullptr,
|
||||
|
|
@ -30763,6 +30782,49 @@ TestCase ImageSamplePackedUintConvertsSampleAndGather() {
|
|||
return test;
|
||||
}
|
||||
|
||||
template <bool rg> TestCase ImageSampleUScaled8() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = rg ? "ImageSampleRG8UScaled" : "ImageSampleR8UScaled";
|
||||
AppendVMovLiteral(&test.code, 20, std::bit_cast<u32>(0.375f));
|
||||
AppendVMovLiteral(&test.code, 21, std::bit_cast<u32>(0.375f));
|
||||
test.code.push_back(EncodeMimg0(0x20, 0xf));
|
||||
test.code.push_back(EncodeMimg1(0, 20));
|
||||
test.code.push_back(EncodeMimg0(0x47, 0x1));
|
||||
test.code.push_back(EncodeMimg1(4, 20));
|
||||
test.code.push_back(EncodeMimg0(0x47, 0x8));
|
||||
test.code.push_back(EncodeMimg1(8, 20));
|
||||
for (u32 component = 0; component < 12; ++component) {
|
||||
AppendStoreVgpr(&test.code, component, component);
|
||||
}
|
||||
AppendEnd(&test.code);
|
||||
// R varies horizontally; G varies vertically in the opposite direction.
|
||||
// Bilinear samples must retain fractions despite host UNorm filter precision.
|
||||
test.image_width = test.image_height = 2;
|
||||
test.sampled_image_rgba = rg ? std::vector<u32>{0xffffff00u, 0x00ff0000u}
|
||||
: std::vector<u32>{0xff00ff00u};
|
||||
test.sampled_image_format = rg ? vk::Format::eR8G8Unorm : vk::Format::eR8Unorm;
|
||||
test.sampled_image_dwords_per_pixel = 1;
|
||||
test.sampler_filter = vk::Filter::eLinear;
|
||||
test.expected_float_tolerance = 0.01f;
|
||||
test.user_data = MakeSampledTextureData(rg ? Prospero::BufferFormat::k8_8UScaled
|
||||
: Prospero::BufferFormat::k8UScaled);
|
||||
test.user_data[1] |= 1u << 30u;
|
||||
test.user_data[2] = 1u << 14u;
|
||||
test.user_data[50] = 12u * sizeof(u32);
|
||||
test.user_data[51] = 3u << 28u;
|
||||
test.has_user_data = true;
|
||||
test.image_descriptor_swizzle = DstSel(rg ? 5 : 4, 4, 0, 1);
|
||||
for (float value : {rg ? 191.25f : 63.75f, 63.75f, 0.0f, 1.0f,
|
||||
0.0f, rg ? 0.0f : 255.0f, 255.0f, rg ? 255.0f : 0.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f}) {
|
||||
test.expected.push_back(std::bit_cast<u32>(value));
|
||||
}
|
||||
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4_LZ,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageLoadR128IgnoresAdjacentMaskSgprs() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
|
|
@ -33685,6 +33747,8 @@ std::vector<TestCase> MakeCases() {
|
|||
AddCase(ImageLoadR32SintUsesSignedSampledImage);
|
||||
AddCase(ImageLoadPackedUintUnpacksAndSwizzles);
|
||||
AddCase(ImageSamplePackedUintConvertsSampleAndGather);
|
||||
AddCase(ImageSampleUScaled8<false>);
|
||||
AddCase(ImageSampleUScaled8<true>);
|
||||
AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs);
|
||||
AddCase(ImageLoad1DUsesScalarCoordinate);
|
||||
AddCase(ImageGather2DInstructionWith1DDescriptor);
|
||||
|
|
@ -38825,9 +38889,17 @@ int main(int argc, char **argv) {
|
|||
RunCase(&vulkan, VectorFloatConversionOps());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--scaled-texture-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, ImageSampleUScaled8<false>());
|
||||
RunCase(&vulkan, ImageSampleUScaled8<true>());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckPackedTextureComponents();
|
||||
RunCase(&vulkan, ImageLoadPackedUintUnpacksAndSwizzles());
|
||||
RunCase(&vulkan, ImageSamplePackedUintConvertsSampleAndGather());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--system-dialog-only") == 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue