Honor explicit gather LOD with shared image mip selection

Co-authored-by: YuhiAida <21134775+YuhiAida@users.noreply.github.com>
Co-authored-by: BlackCAT <52720489+psnwd@users.noreply.github.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
Co-authored-by: Senaxx <michelbeld@gmail.com>
Co-authored-by: Mac <me@itsmemac.com>
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
This commit is contained in:
frans
2026-10-03 11:21:42 +04:00
committed by nmzik
parent 0bb2bdad17
commit bd611bfe15
15 changed files with 517 additions and 171 deletions
@@ -90,8 +90,11 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) { if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
return false; return false;
} }
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read && return ((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
!resource.written && !resource.atomic; resource.mip_count == 1u) ||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic &&
resource.mip_count != 0u)) &&
resource.read && !resource.written && !resource.atomic;
} }
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format, inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
@@ -114,7 +117,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) && resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && ((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
resource.mip_count == 1u) || resource.mip_count == 1u) ||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage && (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic &&
resource.mip_count != 0u)) && resource.mip_count != 0u)) &&
resource.written && resource.written &&
(!resource.atomic || (!resource.atomic ||
@@ -568,7 +568,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u; const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips. // IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
const bool single_storage_mip = const bool single_storage_mip =
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::DynamicStorage; storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::Dynamic;
const auto view_levels = multisampled || single_storage_mip const auto view_levels = multisampled || single_storage_mip
? 1u ? 1u
: static_cast<uint32_t>(last_level - base_level) + 1u; : static_cast<uint32_t>(last_level - base_level) + 1u;
@@ -891,7 +891,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
auto& binding = images[i]; auto& binding = images[i];
binding.mip_views.clear(); binding.mip_views.clear();
const auto& resource = program.info.images[i]; const auto& resource = program.info.images[i];
if (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage) { if (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic) {
EXIT_IF(resource.mip_count == 0u || EXIT_IF(resource.mip_count == 0u ||
resource.mip_count != binding.desc.view_info.level_count); resource.mip_count != binding.desc.view_info.level_count);
binding.mip_views.reserve(resource.mip_count); binding.mip_views.reserve(resource.mip_count);
@@ -899,6 +899,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
auto desc = binding.desc; auto desc = binding.desc;
desc.view_info.base_level += mip; desc.view_info.base_level += mip;
desc.view_info.level_count = 1; desc.view_info.level_count = 1;
// The shader selects the mip after applying the guest minimum LOD.
desc.view_info.min_lod = 0;
binding.mip_views.push_back(texture_cache.FindTexture(binding.image_id, desc)); binding.mip_views.push_back(texture_cache.FindTexture(binding.image_id, desc));
} }
binding.image_view = binding.mip_views.front(); binding.image_view = binding.mip_views.front();
@@ -41,7 +41,7 @@ void ValidateNativeProgram(const IR::Program& program) {
Fail(program, "native shader plan has an invalid image class"); Fail(program, "native shader plan has an invalid image class");
} }
present[static_cast<size_t>(*kind)] = true; present[static_cast<size_t>(*kind)] = true;
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage; const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::Dynamic;
const auto count = dynamic ? program.info.images[i].mip_count : 1u; const auto count = dynamic ? program.info.images[i].mip_count : 1u;
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
Fail(program, "native shader plan has an invalid image mip descriptor count"); Fail(program, "native shader plan has an invalid image mip descriptor count");
@@ -152,20 +152,11 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
} }
} }
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) { uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip) {
const auto& image_resource = state.program.info.images.at(resource); const auto pointer = ImageDescriptorPointer(state, resource, mip);
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
const auto kind = IR::DescriptorBindingForImage(image_resource);
EXIT_IF(!kind.has_value());
const auto array_index = ResourceForDescriptor(state, *kind, resource);
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
const auto pointer_type = state.builder.Type(
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image_resource));
const auto pointer =
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"sampled image descriptor array was not emitted");
const auto image = state.builder.AllocateId(); const auto image = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, ImageType(state, image_resource), image, pointer); state.builder.AddFunction(spv::OpLoad, ImageType(state, state.program.info.images.at(resource)),
image, pointer);
return image; return image;
} }
@@ -183,10 +174,10 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
return sampler_id; return sampler_id;
} }
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler) { uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id, uint32_t mip) {
const auto& image_resource = state.program.info.images.at(resource); const auto& image_resource = state.program.info.images.at(resource);
const auto image = LoadSampledImageDescriptor(state, resource); EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
const auto sampler_id = LoadSamplerDescriptor(state, sampler); const auto image = LoadImageDescriptor(state, resource, mip);
const auto sampled_image = state.builder.AllocateId(); const auto sampled_image = state.builder.AllocateId();
const auto sampled_type = const auto sampled_type =
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource)); state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
@@ -194,17 +185,17 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
return sampled_image; return sampled_image;
} }
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) { uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t mip) {
const auto& image = state.program.info.images.at(resource); const auto& image = state.program.info.images.at(resource);
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage); EXIT_IF(mip >= image.mip_count);
const auto kind = IR::DescriptorBindingForImage(image); const auto kind = IR::DescriptorBindingForImage(image);
EXIT_IF(!kind.has_value()); EXIT_IF(!kind.has_value());
const auto array_index = ResourceForDescriptor(state, *kind, resource); const auto array_index = ResourceForDescriptor(state, *kind, resource) + mip;
const auto pointer_type = state.builder.Type( const auto pointer_type = state.builder.Type(
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image)); spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)]; const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource, return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"storage image descriptor array was not emitted"); "image descriptor array was not emitted");
} }
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord, void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
@@ -214,46 +205,15 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
if (!image.atomic) { if (!image.atomic) {
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat); state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
} }
const auto kind = IR::DescriptorBindingForImage(image); const auto EmitWrite = [&](uint32_t mip) {
EXIT_IF(!kind.has_value()); state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip),
const auto array_index = ResourceForDescriptor(state, *kind, resource); coord, texel);
const auto image_type = ImageType(state, image);
const auto pointer_type =
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, image_type);
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
const auto LoadAt = [&](uint32_t array_index) {
const auto pointer =
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
"storage image descriptor array was not emitted");
const auto descriptor = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, image_type, descriptor, pointer);
return descriptor;
}; };
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) { if (image.mip_mode != IR::ImageMipMode::Dynamic) {
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index), coord, texel); EmitWrite(0);
return; return;
} }
if (image.mip_count == 0u) { EmitImageMipSwitch(state, mip_lod, image.mip_count, 0, EmitWrite);
ExitDescriptorBindingFailure(state, *kind, resource,
"dynamic storage image has no mip descriptors");
}
const auto merge_label = state.builder.AllocateId();
std::vector<uint32_t> labels(image.mip_count);
std::vector<uint32_t> words {spv::OpSwitch, mip_lod, merge_label};
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
labels[mip] = state.builder.AllocateId();
words.push_back(mip);
words.push_back(labels[mip]);
}
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(words);
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
EmitLabel(state, labels[mip]);
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index + mip), coord, texel);
state.builder.AddFunction(spv::OpBranch, merge_label);
}
EmitLabel(state, merge_label);
} }
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) { spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
@@ -3,9 +3,7 @@
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h" #include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
#include <algorithm> #include <algorithm>
#include <atomic>
#include <bit> #include <bit>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter { namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace { namespace {
@@ -86,6 +84,46 @@ uint32_t ZeroF32(EmitterState& state) {
return ConstantF32(state, 0); return ConstantF32(state, 0);
} }
uint32_t GatherMip(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const IR::Inst& address, const ImageSampleLayout& layout, uint32_t mip_count) {
auto& state = ctx.state;
const auto Extract = [&](uint32_t word, uint32_t first, uint32_t count) {
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
ConstantU32(state, count));
};
const auto LodFixed = [&](uint32_t word, uint32_t first) {
return Binary(state, spv::OpFMul, TypeF32(state),
Unary(state, spv::OpConvertUToF, TypeF32(state), Extract(word, first, 12)),
ConstantF32Value(state, 1.0f / 256.0f));
};
const auto sampler_control = ctx.Arg(inst, 6);
const auto preclamp = Binary(state, spv::OpINotEqual, TypeBool(state),
Extract(sampler_control, 28, 1), ConstantU32(state, 0));
const auto half = ConstantF32Value(state, 0.5f);
const auto zero = ZeroF32(state);
const auto base = Unary(state, spv::OpConvertUToF, TypeF32(state),
Extract(ctx.Arg(inst, 4), 12, 4));
const auto minimum = Binary(state, spv::OpFSub, TypeF32(state),
LodFixed(ctx.Arg(inst, 3), 8), base);
const auto last = ConstantF32Value(state, static_cast<float>(mip_count - 1u));
auto lod = Binary(state, spv::OpFAdd, TypeF32(state),
AddressF32(ctx, mem, address, layout.lod),
Select(state, TypeF32(state), preclamp, half, zero));
lod = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
state, lod, LodFixed(ctx.Arg(inst, 5), 0), LodFixed(ctx.Arg(inst, 5), 12));
// T# minimum LOD is in physical mip space and applies after the S# view-relative clamp.
lod = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(state, lod, minimum);
lod = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(state, lod, zero, last);
lod = Binary(state, spv::OpFAdd, TypeF32(state), lod,
Select(state, TypeF32(state), preclamp, zero, half));
// The clamped value is nonnegative, so conversion performs the integer mip rounding.
const auto mip = EmitUMin32(state, Unary(state, spv::OpConvertFToU, TypeU32(state), lod),
ConstantU32(state, mip_count - 1u));
const auto mip_none = Binary(state, spv::OpIEqual, TypeBool(state),
Extract(sampler_control, 26, 2), ConstantU32(state, 0));
return Select(state, TypeU32(state), mip_none, ConstantU32(state, 0), mip);
}
uint32_t CubeAxis(EmitterState& state, uint32_t value) { uint32_t CubeAxis(EmitterState& state, uint32_t value) {
return Binary(state, spv::OpFSub, TypeF32(state), value, ConstantF32(state, 0x3f800000u)); return Binary(state, spv::OpFSub, TypeF32(state), value, ConstantF32(state, 0x3f800000u));
} }
@@ -252,7 +290,7 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
const IR::Inst& address) { const IR::Inst& address) {
const auto dimension = ctx.state.program.info.images.at(mem.resource).dimension; const auto dimension = ctx.state.program.info.images.at(mem.resource).dimension;
const auto& info = ImageDimensionInfoFor(dimension); const auto& info = ImageDimensionInfoFor(dimension);
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource); const auto image = LoadImageDescriptor(ctx.state, mem.resource);
const auto size = ctx.state.builder.AllocateId(); const auto size = ctx.state.builder.AllocateId();
if (info.multisampled != 0u) { if (info.multisampled != 0u) {
ctx.state.builder.AddFunction(spv::OpImageQuerySize, ctx.state.builder.AddFunction(spv::OpImageQuerySize,
@@ -433,7 +471,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
uint32_t coord, Prospero::TextureNumericClass numeric_class) { uint32_t coord, Prospero::TextureNumericClass numeric_class) {
auto& state = ctx.state; auto& state = ctx.state;
state.builder.RequireCapability(spv::CapabilityImageQuery); state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto image = LoadSampledImageDescriptor(state, mem.resource); const auto image = LoadImageDescriptor(state, mem.resource);
const auto width = state.builder.AllocateId(); const auto width = state.builder.AllocateId();
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image, state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image,
ConstantU32(state, 0)); ConstantU32(state, 0));
@@ -446,7 +484,8 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32), Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
ConstantF32(state, 0x3f000000u))); ConstantF32(state, 0x3f000000u)));
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); const auto sampled = MakeSampledImage(state, mem.resource,
LoadSamplerDescriptor(state, mem.sampler));
const auto vector_type = ImageVectorType(state, numeric_class, 4); const auto vector_type = ImageVectorType(state, numeric_class, 4);
const auto scalar_type = ImageScalarType(state, numeric_class); const auto scalar_type = ImageScalarType(state, numeric_class);
const auto component = const auto component =
@@ -558,7 +597,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (op == IR::ValueOpcode::ImageQueryLod) { if (op == IR::ValueOpcode::ImageQueryLod) {
state.builder.RequireCapability(spv::CapabilityImageQuery); state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto dimension = image.dimension; const auto dimension = image.dimension;
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); const auto sampled = MakeSampledImage(state, mem.resource,
LoadSamplerDescriptor(state, mem.sampler));
const auto lod = state.builder.AllocateId(); const auto lod = state.builder.AllocateId();
state.builder.AddFunction( state.builder.AddFunction(
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled, spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
@@ -588,7 +628,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
EmitValueOrDefaultIfCondition( EmitValueOrDefaultIfCondition(
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4), state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
[&]() { [&]() {
const auto descriptor = LoadSampledImageDescriptor(state, mem.resource); const auto descriptor = LoadImageDescriptor(state, mem.resource);
const auto color = state.builder.AllocateId(); const auto color = state.builder.AllocateId();
const auto coord = CoordU32(ctx, mem, *address, dimension); const auto coord = CoordU32(ctx, mem, *address, dimension);
if (dimension_info.multisampled != 0u) { if (dimension_info.multisampled != 0u) {
@@ -612,7 +652,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto dimension = image.dimension; const auto dimension = image.dimension;
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() { EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto mip_lod = const auto mip_lod =
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::Dynamic
? LodU32(ctx, mem, *address, dimension) ? LodU32(ctx, mem, *address, dimension)
: 0u; : 0u;
const auto coord = CoordU32(ctx, mem, *address, dimension); const auto coord = CoordU32(ctx, mem, *address, dimension);
@@ -635,13 +675,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (op == IR::ValueOpcode::ImageGatherRaw) { if (op == IR::ValueOpcode::ImageGatherRaw) {
const auto coord = CoordF32(ctx, mem, *address, layout.coord, const auto coord = CoordF32(ctx, mem, *address, layout.coord,
dimension_info.coordinate_components, image.cube); dimension_info.coordinate_components, image.cube);
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
if (!warned.test_and_set(std::memory_order_relaxed)) {
std::fputs("Warning: approximating IMAGE_GATHER4_L at mip level 0; explicit LOD is ignored.\n",
stderr);
}
}
if (dimension == ImageDimension::Dim1D) { if (dimension == ImageDimension::Dim1D) {
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) || if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
HasFlag(mem, Decoder::ImageSampleFlagOffset) || HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
@@ -658,44 +691,48 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "has an unsupported 1D-array gather"); ctx.Fail(inst, "has an unsupported 1D-array gather");
return; return;
} }
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
const auto sample = state.builder.AllocateId(); : numeric_class;
std::vector<uint32_t> words; const auto result_type = ImageVectorType(state, result_numeric_class, 4);
uint32_t component_or_dref;
if (dref) { if (dref) {
auto dref_value = ZeroF32(state); component_or_dref = layout.dref != NoImageComponent
if (layout.dref != NoImageComponent) { ? AddressF32(ctx, mem, *address, layout.dref)
dref_value = AddressF32(ctx, mem, *address, layout.dref); : ZeroF32(state);
}
words = {spv::OpImageDrefGather,
TypeF32Vector(state, 4),
sample,
sampled,
coord,
dref_value};
} else { } else {
uint32_t component = 0; component_or_dref = ConstantU32(
if (ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid) { state, ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
component = ImageGatherComponent(mem.dmask); ? ImageGatherComponent(mem.dmask) : 0u);
}
words = {spv::OpImageGather,
ImageVectorType(state, numeric_class, 4),
sample,
sampled,
coord,
ConstantU32(state, component)};
} }
uint32_t operand_mask = 0;
uint32_t offset = 0;
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) { if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
words.push_back(spv::ImageOperandsConstOffsetsMask); operand_mask = spv::ImageOperandsConstOffsetsMask;
words.push_back(HorizontalOffsets(state, dimension)); offset = HorizontalOffsets(state, dimension);
} else if (layout.offset != NoImageComponent) { } else if (layout.offset != NoImageComponent) {
words.push_back(spv::ImageOperandsOffsetMask); operand_mask = spv::ImageOperandsOffsetMask;
words.push_back(PackedOffset(ctx, mem, *address, layout, dimension)); offset = PackedOffset(ctx, mem, *address, layout, dimension);
}
state.builder.AddFunction(words);
auto result_numeric_class = numeric_class;
if (dref) {
result_numeric_class = Prospero::TextureNumericClass::Float;
} }
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
const auto EmitGather = [&](uint32_t mip) {
const auto sampled = MakeSampledImage(state, mem.resource, sampler_id, mip);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words {
dref ? spv::OpImageDrefGather : spv::OpImageGather, result_type,
sample, sampled, coord, component_or_dref};
if (operand_mask != 0u) {
words.push_back(operand_mask);
words.push_back(offset);
}
state.builder.AddFunction(words);
return sample;
};
const auto sample = image.mip_mode == IR::ImageMipMode::Dynamic && image.mip_count > 1u
? EmitImageMipSwitch(
state, GatherMip(ctx, inst, mem, *address, layout,
image.mip_count),
image.mip_count, result_type, EmitGather)
: EmitGather(0);
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
result_numeric_class, false, mem, true)); result_numeric_class, false, mem, true));
return; return;
@@ -738,13 +775,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operand_mask |= spv::ImageOperandsBiasMask; operand_mask |= spv::ImageOperandsBiasMask;
operands.push_back(AddressF32(ctx, mem, *address, layout.bias)); operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
} }
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
const auto EmitSample = [&](uint32_t resource) { const auto EmitSample = [&](uint32_t resource) {
const auto& candidate = state.program.info.images[resource]; const auto& candidate = state.program.info.images[resource];
const auto coord = const auto coord =
CoordF32(ctx, mem, *address, layout.coord, CoordF32(ctx, mem, *address, layout.coord,
ImageDimensionInfoFor(candidate.dimension).coordinate_components, ImageDimensionInfoFor(candidate.dimension).coordinate_components,
candidate.cube); candidate.cube);
const auto sampled = MakeSampledImage(state, resource, mem.sampler); const auto sampled = MakeSampledImage(state, resource, sampler_id);
const auto sample = state.builder.AllocateId(); const auto sample = state.builder.AllocateId();
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord}; std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
if (dref) { if (dref) {
@@ -874,7 +912,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
spv::OpTypePointer, spv::StorageClassImage, spv::OpTypePointer, spv::StorageClassImage,
image.atomic64 ? TypeScalarU64(state) : TypeU32(state)); image.atomic64 ? TypeScalarU64(state) : TypeU32(state));
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer, state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
StorageImageDescriptorPointer(state, mem.resource), ImageDescriptorPointer(state, mem.resource),
CoordU32(ctx, mem, *address, dimension), CoordU32(ctx, mem, *address, dimension),
ConstantU32(state, 0)); ConstantU32(state, 0));
if (op == IR::ValueOpcode::ImageAtomicFMin32 || if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
@@ -19,6 +19,7 @@
#include <map> #include <map>
#include <spirv/unified1/GLSL.std.450.h> #include <spirv/unified1/GLSL.std.450.h>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -314,13 +315,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image);
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension); uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension);
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource); uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t mip = 0);
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip = 0);
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler); uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler); uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id,
uint32_t mip = 0);
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord, void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
uint32_t texel); uint32_t texel);
@@ -514,6 +516,45 @@ uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value);
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high); uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high);
// These templates accept local lambdas from several emitter translation units. // These templates accept local lambdas from several emitter translation units.
template <typename Fn>
auto EmitImageMipSwitch(EmitterState& state, uint32_t mip_lod, uint32_t mip_count,
uint32_t result_type, Fn&& emit) {
constexpr bool has_result = !std::is_void_v<std::invoke_result_t<Fn, uint32_t>>;
EXIT_IF(mip_count == 0u);
const auto merge_label = state.builder.AllocateId();
std::vector<uint32_t> labels(mip_count);
std::vector<uint32_t> words {spv::OpSwitch, mip_lod, merge_label};
for (uint32_t mip = 0; mip < mip_count; mip++) {
labels[mip] = state.builder.AllocateId();
words.push_back(mip);
words.push_back(labels[mip]);
}
if constexpr (has_result) {
words[2] = labels.front();
}
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
state.builder.AddFunction(words);
std::vector<uint32_t> phi;
if constexpr (has_result) {
phi = {spv::OpPhi, result_type, state.builder.AllocateId()};
}
for (uint32_t mip = 0; mip < mip_count; mip++) {
EmitLabel(state, labels[mip]);
if constexpr (has_result) {
phi.push_back(emit(mip));
phi.push_back(state.current_label);
} else {
emit(mip);
}
state.builder.AddFunction(spv::OpBranch, merge_label);
}
EmitLabel(state, merge_label);
if constexpr (has_result) {
state.builder.AddFunction(phi);
return phi[2];
}
}
template <typename Fn> template <typename Fn>
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) { void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
const auto then_label = state.builder.AllocateId(); const auto then_label = state.builder.AllocateId();
@@ -708,8 +708,16 @@ void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
const auto resource = GetImageResource(memory); const auto resource = GetImageResource(memory);
const auto sampler = GetSamplerResource(memory); const auto sampler = GetSamplerResource(memory);
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0)); const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
const auto result = ir.Emit(IR::ValueOpcode::ImageGatherRaw, {resource, sampler, address}, const bool has_lod = (memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u;
AddMemoryInfo(memory, inst.pc)); // LOD selection consumes these words on the GPU after descriptor handles are stripped.
const auto result = ir.Emit(
IR::ValueOpcode::ImageGatherRaw,
{resource, sampler, address,
has_lod ? resource.Instruction()->Arg(1) : IR::Value(0u),
has_lod ? resource.Instruction()->Arg(3) : IR::Value(0u),
has_lod ? sampler.Instruction()->Arg(1) : IR::Value(0u),
has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u)},
AddMemoryInfo(memory, inst.pc));
for (uint32_t index = 0; index < memory.data_dwords; index++) { for (uint32_t index = 0; index < memory.data_dwords; index++) {
WriteOperand(OffsetOperand(inst.dst, index), WriteOperand(OffsetOperand(inst.dst, index),
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)})); ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)}));
+2 -1
View File
@@ -114,7 +114,7 @@ struct BufferResource {
bool operator==(const BufferResource& other) const = default; bool operator==(const BufferResource& other) const = default;
}; };
enum class ImageMipMode { None, DynamicStorage }; enum class ImageMipMode { None, Dynamic };
constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu; constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu;
@@ -151,6 +151,7 @@ struct SamplerResource {
bool force_point_filtering = false; bool force_point_filtering = false;
bool depth_compare = false; bool depth_compare = false;
bool integer_border = false; bool integer_border = false;
bool gather_lod = false;
bool operator==(const SamplerResource& other) const = default; bool operator==(const SamplerResource& other) const = default;
}; };
@@ -297,7 +297,7 @@ VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1) VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1) VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32)
VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1) VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1)
VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1)
VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1) VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1)
@@ -126,7 +126,7 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
EXIT("shader binding layout failed: image %u has an unmapped binding class", i); EXIT("shader binding layout failed: image %u has an unmapped binding class", i);
} }
auto& resources = image_groups[group]; auto& resources = image_groups[group];
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage; const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::Dynamic;
const auto count = dynamic ? program.info.images[i].mip_count : 1u; const auto count = dynamic ? program.info.images[i].mip_count : 1u;
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) { if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i, EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i,
@@ -124,8 +124,8 @@ bool DescriptorIsCube(const DescriptorValue& descriptor) {
Prospero::ImageType::kCube; Prospero::ImageType::kCube;
} }
uint32_t StorageMipCount(const ImageResource& image, const DescriptorValue& descriptor) { uint32_t ImageMipCount(const ImageResource& image, const DescriptorValue& descriptor) {
if (image.mip_mode != ImageMipMode::DynamicStorage || NullImageDescriptor(descriptor)) { if (image.mip_mode != ImageMipMode::Dynamic || NullImageDescriptor(descriptor)) {
return 1; return 1;
} }
const auto base = (descriptor.dwords[3] >> 12u) & 0xfu; const auto base = (descriptor.dwords[3] >> 12u) & 0xfu;
@@ -435,10 +435,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
(base.atomic && base.resource_class != ImageResourceClass::Storage)) { (base.atomic && base.resource_class != ImageResourceClass::Storage)) {
return SpecializationFail(fmt::format("image resource {} has an invalid class", i)); return SpecializationFail(fmt::format("image resource {} has an invalid class", i));
} }
image.mip_count = StorageMipCount(base, descriptor); image.mip_count = ImageMipCount(base, descriptor);
if (image.mip_count == 0u) { if (image.mip_count == 0u) {
return SpecializationFail( return SpecializationFail(
fmt::format("storage image descriptor {} has an invalid mip range", i)); fmt::format("image descriptor {} has an invalid mip range", i));
} }
if (NullImageDescriptor(descriptor)) { if (NullImageDescriptor(descriptor)) {
image.numeric_class = base.atomic ? Prospero::TextureNumericClass::Uint image.numeric_class = base.atomic ? Prospero::TextureNumericClass::Uint
@@ -1077,6 +1077,16 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) { if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) {
return false; return false;
} }
if (program.info.samplers[i].gather_lod) {
const auto control = snapshot.samplers[i].dwords[2];
const auto filter = (control >> 26u) & 3u;
// MipNone always selects the base level. Explicit point gathers currently require
// encoded-zero primary and secondary bias; linear primary-mip selection is unsupported.
if (filter > 1u || (filter == 1u && (control & 0xfffffu) != 0u)) {
return SpecializationFail(
"explicit-LOD gather requires mip filtering None or Point with zero LOD biases");
}
}
} }
snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end()); snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
return BuildResourceSpecialization(program, snapshot, specialization); return BuildResourceSpecialization(program, snapshot, specialization);
@@ -1727,9 +1727,11 @@ private:
const auto resource_class = ImageOpcodeInfoOf(op).resource_class; const auto resource_class = ImageOpcodeInfoOf(op).resource_class;
const bool atomic64 = ImageOpcodeInfoOf(op).access == ImageAccess::Atomic && const bool atomic64 = ImageOpcodeInfoOf(op).access == ImageAccess::Atomic &&
memory.data_bits == 64u; memory.data_bits == 64u;
const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip const bool dynamic_mip =
? ImageMipMode::DynamicStorage (resource_class == ImageResourceClass::Storage && memory.image_has_mip) ||
: ImageMipMode::None; (op == ValueOpcode::ImageGatherRaw &&
(memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u);
const auto mip = dynamic_mip ? ImageMipMode::Dynamic : ImageMipMode::None;
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0; const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
for (uint32_t i = 0; i < m_info.images.size(); i++) { for (uint32_t i = 0; i < m_info.images.size(); i++) {
auto& image = m_info.images[i]; auto& image = m_info.images[i];
@@ -1939,6 +1941,9 @@ private:
if (sampler == UINT32_MAX) { if (sampler == UINT32_MAX) {
Fail(flags.pc, "sampler resource limit exceeded"); Fail(flags.pc, "sampler resource limit exceeded");
} }
m_info.samplers[sampler].gather_lod |=
op == ValueOpcode::ImageGatherRaw &&
(memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u;
AddHandlePatch(sampler_handle, sampler, flags.pc); AddHandlePatch(sampler_handle, sampler, flags.pc);
AddSampledPair(resource, sampler, flags.pc); AddSampledPair(resource, sampler, flags.pc);
} }
+54 -1
View File
@@ -2032,6 +2032,58 @@ void TestFmaskLoadSpecialization() {
"rebinding FMASK as a texture reused the metadata specialization"); "rebinding FMASK as a texture reused the metadata specialization");
} }
void TestGatherLodSamplerValidation() {
for (const bool explicit_lod : {false, true}) {
for (const bool reverse : {false, true}) {
Fixture fixture;
std::array<Value, 8> image_words;
std::array<Value, 4> sampler_words;
for (uint32_t i = 0; i < image_words.size(); i++)
image_words[i] = fixture.UserData(i);
for (uint32_t i = 0; i < sampler_words.size(); i++)
sampler_words[i] = fixture.UserData(i + 8);
for (uint32_t i = 0; i < 2; i++) {
const bool lod = explicit_lod && i == (reverse ? 0u : 1u);
MemoryInfo memory;
memory.kind = ResourceKind::Image;
memory.image_dimension = Decoder::ImageDimension::Dim2D;
memory.image_sample_flags = lod ? Decoder::ImageSampleFlagLod
: Decoder::ImageSampleFlagLevelZero;
fixture.Emit(ValueOpcode::ImageGatherRaw,
{fixture.Image(image_words), fixture.Sampler(sampler_words),
fixture.ImageAddress(), image_words[1], image_words[3],
sampler_words[1], sampler_words[2]},
fixture.AddMemory(memory, i * 4));
}
fixture.PlanAndTrack();
auto plan = ExtractResourcePlan(fixture.program);
Check(plan.info.samplers.size() == 1 &&
plan.info.samplers[0].gather_lod == explicit_lod,
"shared sampler lost explicit gather validation or restricted LZ gather");
std::array<uint32_t, 12> user_data{};
user_data[0] = 0x1000u;
user_data[1] = static_cast<uint32_t>(
Libs::Graphics::Prospero::BufferFormat::k32Float) << 20u;
user_data[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
user_data[9] = 0xfff000u;
constexpr std::array sampler_controls{
0u, 0x4001u, 1u << 26u, (1u << 26u) | 256u,
(1u << 26u) | (1u << 14u), 2u << 26u, 3u << 26u};
for (const auto control : sampler_controls) {
user_data[10] = control;
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
const bool supported = !explicit_lod || (control >> 26u) == 0 ||
control == (1u << 26u);
Check(MaterializeResources(plan, {.user_data = user_data}, snapshot,
specialization) == supported,
"explicit gather accepted an unsupported sampler or rejected a valid one");
}
}
}
}
void TestDynamicStorageMipTracking() { void TestDynamicStorageMipTracking() {
Fixture fixture; Fixture fixture;
std::array<Value, 8> image_words; std::array<Value, 8> image_words;
@@ -2067,7 +2119,7 @@ void TestDynamicStorageMipTracking() {
const auto &images = fixture.program.info.images; const auto &images = fixture.program.info.images;
Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None && Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None &&
images[0].mip_count == 1 && images[0].mip_count == 1 &&
images[1].mip_mode == ImageMipMode::DynamicStorage && images[1].mip_mode == ImageMipMode::Dynamic &&
images[1].mip_count == 1, images[1].mip_count == 1,
"storage mip writes did not share one dynamic logical resource"); "storage mip writes did not share one dynamic logical resource");
Check(plain.first.Instruction()->Flags<uint32_t>() == 0 && Check(plain.first.Instruction()->Flags<uint32_t>() == 0 &&
@@ -3412,6 +3464,7 @@ int main() {
Run("images and samplers", TestImagesSamplersAndAliases); Run("images and samplers", TestImagesSamplersAndAliases);
Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch); Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch);
Run("FMASK load specialization", TestFmaskLoadSpecialization); Run("FMASK load specialization", TestFmaskLoadSpecialization);
Run("gather LOD sampler validation", TestGatherLodSamplerValidation);
Run("dynamic storage mips", TestDynamicStorageMipTracking); Run("dynamic storage mips", TestDynamicStorageMipTracking);
Run("invariant indirect images", TestInvariantIndirectImageMaterialization); Run("invariant indirect images", TestInvariantIndirectImageMaterialization);
Run("guarded direct image table", TestGuardedDirectImageTable); Run("guarded direct image table", TestGuardedDirectImageTable);
+265 -41
View File
@@ -1175,6 +1175,7 @@ struct TestCase {
vk::ImageType sampled_image_type = vk::ImageType::e2D; vk::ImageType sampled_image_type = vk::ImageType::e2D;
vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D; vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D;
u32 sampled_image_layers = 1; u32 sampled_image_layers = 1;
u32 sampled_image_view_base_mip = 0;
u32 sampled_image_view_base_layer = 0; u32 sampled_image_view_base_layer = 0;
u32 sampled_image_view_layers = 0; u32 sampled_image_view_layers = 0;
std::vector<u32> storage_image_rgba; std::vector<u32> storage_image_rgba;
@@ -1207,7 +1208,7 @@ struct TestCase {
std::vector<u32> expected_gds; std::vector<u32> expected_gds;
std::vector<std::pair<std::string, size_t>> decoded_counts; std::vector<std::pair<std::string, size_t>> decoded_counts;
std::vector<std::pair<std::string, size_t>> ir_counts; std::vector<std::pair<std::string, size_t>> ir_counts;
u32 expected_storage_mip_descriptors = 0; u32 expected_mip_descriptors = 0;
std::optional<std::vector<u32>> expected_buffer_resources; std::optional<std::vector<u32>> expected_buffer_resources;
}; };
@@ -1517,28 +1518,26 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
text + " count=" + std::to_string(actual) + text + " count=" + std::to_string(actual) +
", expected=" + std::to_string(expected)); ", expected=" + std::to_string(expected));
} }
if (test.expected_storage_mip_descriptors != 0) { if (test.expected_mip_descriptors != 0) {
const auto image = std::ranges::find_if( const auto image = std::ranges::find_if(
result.program.info.images, [](const auto &resource) { result.program.info.images, [](const auto &resource) {
return resource.mip_mode == return resource.mip_mode ==
ShaderRecompiler::IR::ImageMipMode::DynamicStorage; ShaderRecompiler::IR::ImageMipMode::Dynamic;
}); });
Require(test.name, "dynamic storage mip specialization", Require(test.name, "dynamic mip specialization",
image != result.program.info.images.end() && image != result.program.info.images.end() &&
image->mip_count == test.expected_storage_mip_descriptors, image->mip_count == test.expected_mip_descriptors,
"runtime descriptor range did not specialize the mip count"); "runtime descriptor range did not specialize the mip count");
const auto resource = const auto resource =
static_cast<u32>(image - result.program.info.images.begin()); static_cast<u32>(image - result.program.info.images.begin());
const auto binding = std::ranges::find_if( const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(*image);
result.program.bindings.descriptors, [&](const auto &group) { const auto *binding = kind ? ShaderRecompiler::IR::FindBinding(
return std::ranges::find(group.resources, resource) != result.program.bindings, *kind) : nullptr;
group.resources.end(); Require(test.name, "dynamic mip binding",
}); binding != nullptr &&
Require(test.name, "dynamic storage mip binding",
binding != result.program.bindings.descriptors.end() &&
std::ranges::count(binding->resources, resource) == std::ranges::count(binding->resources, resource) ==
test.expected_storage_mip_descriptors, test.expected_mip_descriptors,
"dynamic storage image did not receive one descriptor per mip"); "dynamic image did not receive one descriptor per mip");
} }
if (test.expand_shader_data_storage) { if (test.expand_shader_data_storage) {
auto &block = *result.program.blocks.front(); auto &block = *result.program.blocks.front();
@@ -11225,7 +11224,7 @@ public:
"IMAGE_STORE expanded an inaccessible mip and shifted physical storage"); "IMAGE_STORE expanded an inaccessible mip and shifted physical storage");
auto mipped_storage_resource = storage_resource; auto mipped_storage_resource = storage_resource;
mipped_storage_resource.mip_mode = mipped_storage_resource.mip_mode =
ShaderRecompiler::IR::ImageMipMode::DynamicStorage; ShaderRecompiler::IR::ImageMipMode::Dynamic;
mipped_storage_resource.mip_count = 3; mipped_storage_resource.mip_count = 3;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
ExpectFatal("MipViewPhysicalLayoutChange", [&] { ExpectFatal("MipViewPhysicalLayoutChange", [&] {
@@ -11514,6 +11513,33 @@ public:
views[2] != views[3] && views[1] == views.back(), views[2] != views[3] && views[1] == views.back(),
"different texture clamps aliased or identical clamped views " "different texture clamps aliased or identical clamped views "
"were not reused"); "were not reused");
ShaderRecompiler::IR::CompiledShaderInfo gather_info{};
gather_info.stage = ShaderType::Compute;
gather_info.info = lod_program.program.info;
gather_info.info.images[0].mip_mode =
ShaderRecompiler::IR::ImageMipMode::Dynamic;
gather_info.info.images[0].mip_count = 4;
auto gather_snapshot = lod_program.resources;
gather_snapshot.images[0] = lod_descriptor;
ShaderStageRuntime gather_runtime{&gather_info, &gather_snapshot};
PreparedBindings gather_bindings;
executor.PrepareBindings(gather_runtime, gather_bindings);
executor.RebindImages(gather_bindings);
const auto &gather_binding = gather_bindings.images[0];
Require(lod_test.name, "dynamic sampled mip views",
gather_binding.desc.view_info.min_lod == 256u &&
gather_binding.mip_views.size() == 4,
"dynamic gather lost the guest minimum or accessible mip range");
for (uint32_t mip = 0; mip < 4; ++mip) {
auto single_mip = gather_binding.desc;
single_mip.view_info.base_level = mip;
single_mip.view_info.level_count = 1;
single_mip.view_info.min_lod = 0;
Require(lod_test.name, "gather minimum applied once",
gather_binding.mip_views[mip] == texture_cache.FindTexture(
gather_binding.image_id, single_mip),
"the gather mip view reapplied the shader's streaming clamp");
}
DestroyBuffer(&output); DestroyBuffer(&output);
RenderExecutorTestAccess::ResetBindings(executor); RenderExecutorTestAccess::ResetBindings(executor);
} }
@@ -13786,7 +13812,8 @@ public:
u32 layers, u32 view_base_layer = 0, u32 layers, u32 view_base_layer = 0,
u32 view_layers = 0, u32 view_layers = 0,
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1, vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1,
vk::Format view_format = vk::Format::eUndefined) { vk::Format view_format = vk::Format::eUndefined,
u32 view_base_mip = 0) {
Image ret; Image ret;
ret.format = format; ret.format = format;
ret.width = width; ret.width = width;
@@ -13845,8 +13872,10 @@ public:
view_info.viewType = view_type; view_info.viewType = view_type;
view_info.format = view_format == vk::Format::eUndefined ? format : view_format; view_info.format = view_format == vk::Format::eUndefined ? format : view_format;
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
view_info.subresourceRange.baseMipLevel = 0; Require(shader_name, "dispatch", view_base_mip < ret.mip_levels,
view_info.subresourceRange.levelCount = ret.mip_levels; "image view base mip is out of bounds");
view_info.subresourceRange.baseMipLevel = view_base_mip;
view_info.subresourceRange.levelCount = ret.mip_levels - view_base_mip;
Require(shader_name, "dispatch", view_base_layer < layers, Require(shader_name, "dispatch", view_base_layer < layers,
"image view base layer is out of bounds"); "image view base layer is out of bounds");
if (view_layers == 0) { if (view_layers == 0) {
@@ -14134,6 +14163,7 @@ public:
std::vector<vk::WriteDescriptorSet> writes; std::vector<vk::WriteDescriptorSet> writes;
std::vector<vk::DescriptorBufferInfo> buffer_infos; std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> sampled_infos; std::vector<vk::DescriptorImageInfo> sampled_infos;
std::vector<vk::ImageView> sampled_mip_views;
std::vector<vk::DescriptorImageInfo> storage_infos; std::vector<vk::DescriptorImageInfo> storage_infos;
std::vector<vk::DescriptorImageInfo> storage_uint_infos; std::vector<vk::DescriptorImageInfo> storage_uint_infos;
std::vector<vk::DescriptorImageInfo> storage_atomic_infos; std::vector<vk::DescriptorImageInfo> storage_atomic_infos;
@@ -14271,9 +14301,32 @@ public:
"sampled image descriptor requested but no sampled image was " "sampled image descriptor requested but no sampled image was "
"provided"); "provided");
sampled_infos.resize(sampled->resources.size()); sampled_infos.resize(sampled->resources.size());
for (auto &info : sampled_infos) { std::vector<u32> mip_indices(compiled.program.info.images.size());
for (u32 slot = 0; slot < sampled_infos.size(); slot++) {
auto &info = sampled_infos[slot];
info.imageView = sampled_image->view; info.imageView = sampled_image->view;
info.imageLayout = sampled_image->layout; info.imageLayout = sampled_image->layout;
const auto resource = sampled->resources[slot];
if (compiled.program.info.images[resource].mip_mode ==
ShaderRecompiler::IR::ImageMipMode::Dynamic) {
const auto mip = test.sampled_image_view_base_mip + mip_indices[resource]++;
Require(test.name, "dispatch", mip < sampled_image->mip_levels,
"sampled mip descriptor exceeds the supplied image");
vk::ImageViewCreateInfo view{};
view.image = sampled_image->image;
view.viewType = test.sampled_image_view_type;
view.format = sampled_image->format;
view.subresourceRange = {
vk::ImageAspectFlagBits::eColor, mip, 1,
test.sampled_image_view_base_layer,
test.sampled_image_view_layers != 0
? test.sampled_image_view_layers
: sampled_image->layers - test.sampled_image_view_base_layer};
RequireVk(test.name, "dispatch",
m_device.createImageView(&view, nullptr, &info.imageView),
"vkCreateImageView(sampled mip)");
sampled_mip_views.push_back(info.imageView);
}
} }
vk::WriteDescriptorSet write{}; vk::WriteDescriptorSet write{};
write.sType = vk::StructureType::eWriteDescriptorSet; write.sType = vk::StructureType::eWriteDescriptorSet;
@@ -14428,6 +14481,9 @@ public:
&barrier, 0, nullptr); &barrier, 0, nullptr);
} }
EndSubmitAndFree(test.name, "dispatch", cmd); EndSubmitAndFree(test.name, "dispatch", cmd);
for (const auto view : sampled_mip_views) {
m_device.destroyImageView(view, nullptr);
}
if (flattened_buffer.buffer != nullptr) { if (flattened_buffer.buffer != nullptr) {
DestroyBuffer(&flattened_buffer); DestroyBuffer(&flattened_buffer);
} }
@@ -17346,7 +17402,8 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
sampled_dwords_per_pixel, vk::ImageLayout::eShaderReadOnlyOptimal, sampled_dwords_per_pixel, vk::ImageLayout::eShaderReadOnlyOptimal,
test.sampled_image_type, test.sampled_image_view_type, test.sampled_image_type, test.sampled_image_view_type,
test.sampled_image_layers, test.sampled_image_view_base_layer, test.sampled_image_layers, test.sampled_image_view_base_layer,
test.sampled_image_view_layers); test.sampled_image_view_layers, vk::SampleCountFlagBits::e1,
vk::Format::eUndefined, test.sampled_image_view_base_mip);
} }
if (needs_storage_image) { if (needs_storage_image) {
const bool atomic64 = std::ranges::any_of( const bool atomic64 = std::ranges::any_of(
@@ -17365,7 +17422,8 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
atomic64 ? vk::Format::eR64Uint : vk::Format::eR32Uint); atomic64 ? vk::Format::eR64Uint : vk::Format::eR32Uint);
} }
if (needs_sampler) { if (needs_sampler) {
sampler = vulkan->CreateNearestSampler(test.name, sampled_image.mip_levels); sampler = vulkan->CreateNearestSampler(
test.name, sampled_image.mip_levels - test.sampled_image_view_base_mip);
} }
vulkan->Dispatch(test, compiled, buffer, needs_gds ? &gds_buffer : nullptr, vulkan->Dispatch(test, compiled, buffer, needs_gds ? &gds_buffer : nullptr,
@@ -29981,34 +30039,135 @@ TestCase ImageCubeGradientsPreserveDerivatives() {
return test; return test;
} }
TestCase ImageGatherLodApproximatesLevelZero() { TestCase ImageGatherExplicitLod() {
using O = ShaderOpcode; using O = ShaderOpcode;
std::vector<u32> code; std::vector<u32> code;
AppendVMovLiteral(&code, 20, 0x3f000000u); // PPSA29343 uses NSA coordinates v81/v84 with its explicit LOD in v78.
AppendVMovLiteral(&code, 21, 0x3f000000u); AppendVMovLiteral(&code, 81, 0x3f000000u);
AppendVMovLiteral(&code, 22, 0x3f800000u); AppendVMovLiteral(&code, 84, 0x3f000000u);
code.push_back(EncodeMimg0(0x44, 0x1)); AppendVMovLiteral(&code, 78, 0x3f800000u);
code.push_back(EncodeMimg1(0, 20)); code.push_back(EncodeMimg0(0x44, 0x1, 1));
code.push_back(EncodeMimg0(0x24, 0x1)); code.push_back(EncodeMimg1(9, 81, 0, 2));
code.push_back(EncodeMimg1(4, 20)); code.push_back(84u | (78u << 8u));
code.push_back(EncodeMimg0(0x24, 0x1, 1));
code.push_back(EncodeMimg1(13, 81, 0, 2));
code.push_back(84u | (78u << 8u));
for (u32 i = 0; i < 5u; i++) { for (u32 i = 0; i < 5u; i++) {
AppendStoreVgpr(&code, i, i); AppendStoreVgpr(&code, i + 9, i);
} }
AppendEnd(&code); AppendEnd(&code);
TestCase test; TestCase test;
test.name = "ImageGatherLodApproximatesLevelZero"; test.name = "ImageGatherExplicitLod";
test.code = std::move(code); test.code = std::move(code);
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_L, O::IMAGE_SAMPLE, test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_L, O::IMAGE_SAMPLE,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}; O::BUFFER_STORE_DWORD, O::S_ENDPGM};
// Explicit sampling proves mip 1 is accessible; the gather must still read mip 0. // A gather's single-mip descriptors must not replace the ordinary sample's
test.expected = {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x41000000u}; // full mip chain when both instructions use the same guest descriptor.
test.expected = std::vector<u32>(5, 0x41000000u);
test.sampled_image_rgba_mips = {std::vector<u32>(4 * 4 * 4, 0x3f800000u), test.sampled_image_rgba_mips = {std::vector<u32>(4 * 4 * 4, 0x3f800000u),
std::vector<u32>(2 * 2 * 4, 0x41000000u)}; std::vector<u32>(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; // Captured point sampler, preclamp disabled.
test.user_data[50] = test.expected.size() * sizeof(u32);
test.user_data[51] = 3u << 28u;
test.has_user_data = true;
test.expected_mip_descriptors = 2;
test.required_spirv = {"OpImageGather", "OpImageSampleExplicitLod"}; test.required_spirv = {"OpImageGather", "OpImageSampleExplicitLod"};
return test; return test;
} }
template <u32 mode> TestCase ImageGatherLodPerLane() {
using O = ShaderOpcode;
constexpr std::array names{
"ImageGatherLodPerLane", "ImageGatherLodBaseMip",
"ImageGatherLodSamplerBounds", "ImageGatherLodTextureMinimum",
"ImageGatherLodMipNone", "ImageGatherLodPointPostclampMaximum",
"ImageGatherLodPointPreclampMaximum", "ImageGatherLodPointPostclampMinimum",
"ImageGatherLodPointPreclampMinimum"};
constexpr std::array lods = mode < 5
? std::array{-1.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 9.0f}
: std::array{-1.0f, 0.0f, 0.25f, 0.75f, 1.25f, 1.75f, 2.75f, 9.0f};
constexpr std::array<std::array<u32, 8>, 9> selected_mips{{
{0, 0, 1, 2, 3, 4, 4, 4},
{1, 1, 2, 3, 4, 4, 4, 4},
{2, 2, 2, 3, 3, 3, 3, 3},
{3, 3, 3, 3, 3, 3, 3, 3},
{1, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 1, 1, 2, 2, 2},
{0, 0, 0, 1, 1, 1, 1, 1},
{2, 2, 2, 2, 2, 2, 3, 4},
{1, 1, 1, 1, 1, 2, 3, 4},
}};
constexpr u32 lanes = lods.size();
constexpr u32 sampler_min = mode == 2 ? 256 : mode >= 7 ? 448 : 0;
constexpr u32 sampler_max = mode == 2 ? 512 : mode == 3 ? 256
: mode == 5 || mode == 6 ? 448 : 1024;
TestCase test;
test.name = names[mode];
test.image_width = test.image_height = 16;
test.sampled_image_view_base_mip = mode > 0 && mode < 5 ? 1 : 0;
for (u32 mip = 0; mip < 5; mip++) {
test.sampled_image_rgba_mips.emplace_back(
(16u >> mip) * (16u >> mip) * 4u,
std::bit_cast<u32>(static_cast<float>(mip + 1)));
}
for (const auto lod : lods) {
test.initial.push_back(std::bit_cast<u32>(lod));
}
test.expected = test.initial;
for (u32 component = 0; component < 4; component++) {
for (const auto mip : selected_mips[mode]) {
test.expected.push_back(std::bit_cast<u32>(static_cast<float>(mip + 1)));
}
}
auto &code = test.code;
code.push_back(EncodeVop1(0x01, 16, Vgpr(0)));
code.push_back(EncodeVop2(0x1a, 31, InlineU32(2), 16));
code.push_back(EncodeMubuf0(0x0c));
code.push_back(EncodeMubuf1(22, 4, 31));
AppendVMovLiteral(&code, 20, 0x3f000000u);
AppendVMovLiteral(&code, 21, 0x3f000000u);
code.push_back(EncodeMimg0(0x44, 0x1));
code.push_back(EncodeMimg1(0, 20, 0, 2));
for (u32 component = 0; component < 4; component++) {
AppendStoreVgprAtLaneDwordOffset(&code, component, 16, lanes * (component + 1));
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::BUFFER_LOAD_DWORD,
O::IMAGE_GATHER4_L, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float);
test.user_data[1] |= (3u << 30u) | (mode == 3 ? 768u << 8u : 0u);
test.user_data[2] = 3u | (15u << 14u);
test.user_data[3] |= (test.sampled_image_view_base_mip << 12u) | (4u << 16u);
test.user_data[8] = 0x92u;
test.user_data[9] = sampler_min | (sampler_max << 12u);
// Point preclamp selects whether the half-level rounding adjustment precedes
// or follows the clamps. The streaming T# minimum follows the sampler bounds.
test.user_data[10] = mode == 4 ? 0x4001u : (1u << 26u);
if constexpr (mode == 6 || mode == 8) {
test.user_data[10] |= 1u << 28u;
}
test.user_data[18] = lanes * sizeof(u32);
test.user_data[19] = 3u << 28u;
test.user_data[50] = test.expected.size() * sizeof(u32);
test.user_data[51] = 3u << 28u;
test.has_user_data = true;
test.compute_info.threads_num[0] = lanes;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
test.expected_mip_descriptors = 5 - test.sampled_image_view_base_mip;
test.required_spirv = {"OpImageGather"};
return test;
}
TestCase ImageD16GatherPacksHalfPairs() { TestCase ImageD16GatherPacksHalfPairs() {
using O = ShaderOpcode; using O = ShaderOpcode;
@@ -30441,11 +30600,42 @@ TestCase ImageStoreMipSelectsPpsa01340Descriptor() {
std::copy(descriptor.begin(), descriptor.end(), test.user_data.begin()); std::copy(descriptor.begin(), descriptor.end(), test.user_data.begin());
test.has_user_data = true; test.has_user_data = true;
test.compile_only = true; test.compile_only = true;
test.expected_storage_mip_descriptors = 3; test.expected_mip_descriptors = 3;
test.required_spirv = {"OpSwitch", "OpImageWrite"}; test.required_spirv = {"OpSwitch", "OpImageWrite"};
return test; return test;
} }
template <u32 mode> TestCase ImageStoreSingleMipBounds() {
using O = ShaderOpcode;
constexpr std::array names{"ImageStoreSingleMipValid", "ImageStoreSingleMipBeyondEnd",
"ImageStoreSingleMipNegative"};
constexpr std::array lods{0u, 1u, UINT32_MAX};
TestCase test;
test.name = names[mode];
AppendVMovU32(&test.code, 20, 0);
AppendVMovU32(&test.code, 21, 0);
AppendVMovU32(&test.code, 22, lods[mode]);
for (u32 component = 0; component < 4; component++) {
AppendVMovLiteral(&test.code, component, 0x40000000u);
}
test.code.push_back(EncodeMimg0(0x09, 0xf));
test.code.push_back(EncodeMimg1(0, 20));
AppendEnd(&test.code);
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE_MIP, O::S_ENDPGM};
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32_32_32_32Float);
test.has_user_data = true;
test.storage_image_rgba = std::vector<u32>(4 * 4 * 4, 0x3f800000u);
test.expected_storage_image_rgba = test.storage_image_rgba;
if constexpr (mode == 0) {
std::fill_n(test.expected_storage_image_rgba.begin(), 4, 0x40000000u);
}
test.expected_mip_descriptors = 1;
if constexpr (mode == 0) {
test.required_spirv = {"OpImageWrite"};
}
return test;
}
TestCase ImageStoreRgbOneUsesInverseSwizzle() { TestCase ImageStoreRgbOneUsesInverseSwizzle() {
using O = ShaderOpcode; using O = ShaderOpcode;
@@ -32009,7 +32199,16 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageGetResinfoDmaskMipLevels); AddCase(ImageGetResinfoDmaskMipLevels);
AddCase(ImageSampleAndGather); AddCase(ImageSampleAndGather);
AddCase(ImageCubeGradientsPreserveDerivatives); AddCase(ImageCubeGradientsPreserveDerivatives);
AddCase(ImageGatherLodApproximatesLevelZero); AddCase(ImageGatherExplicitLod);
AddCase(ImageGatherLodPerLane<0>);
AddCase(ImageGatherLodPerLane<1>);
AddCase(ImageGatherLodPerLane<2>);
AddCase(ImageGatherLodPerLane<3>);
AddCase(ImageGatherLodPerLane<4>);
AddCase(ImageGatherLodPerLane<5>);
AddCase(ImageGatherLodPerLane<6>);
AddCase(ImageGatherLodPerLane<7>);
AddCase(ImageGatherLodPerLane<8>);
AddCase(ImageD16GatherPacksHalfPairs); AddCase(ImageD16GatherPacksHalfPairs);
AddCase(ImageSampleA16SamplerCoordsOnGpu); AddCase(ImageSampleA16SamplerCoordsOnGpu);
AddCase(ImageSampleOpcodeAliasUsesNormalCoords); AddCase(ImageSampleOpcodeAliasUsesNormalCoords);
@@ -32019,6 +32218,9 @@ std::vector<TestCase> MakeCases() {
AddCase(ImageStoreVariants); AddCase(ImageStoreVariants);
AddCase(ImageD16StoreUnpacksHalfPairs); AddCase(ImageD16StoreUnpacksHalfPairs);
AddCase(ImageStoreMipSelectsPpsa01340Descriptor); AddCase(ImageStoreMipSelectsPpsa01340Descriptor);
AddCase(ImageStoreSingleMipBounds<0>);
AddCase(ImageStoreSingleMipBounds<1>);
AddCase(ImageStoreSingleMipBounds<2>);
AddCase(ImageStoreRgbOneUsesInverseSwizzle); AddCase(ImageStoreRgbOneUsesInverseSwizzle);
AddCase(ImageStoreDuplicateSelectorUsesInverseSwizzle); AddCase(ImageStoreDuplicateSelectorUsesInverseSwizzle);
AddCase(ImageStoreBgraUsesInverseSwizzle); AddCase(ImageStoreBgraUsesInverseSwizzle);
@@ -33060,7 +33262,7 @@ void CheckSampledColorViews() {
"atomic uint storage resource was rejected"); "atomic uint storage resource was rejected");
storage_resource.atomic = false; storage_resource.atomic = false;
storage_resource.mip_mode = storage_resource.mip_mode =
ShaderRecompiler::IR::ImageMipMode::DynamicStorage; ShaderRecompiler::IR::ImageMipMode::Dynamic;
storage_resource.mip_count = 3; storage_resource.mip_count = 3;
Require("SampledColorViews", "dynamic storage mip resource", Require("SampledColorViews", "dynamic storage mip resource",
IsSupportedStorageImageResource(storage_resource), IsSupportedStorageImageResource(storage_resource),
@@ -33133,10 +33335,15 @@ void CheckSampledDepthResource() {
!IsSupportedSampledDepthResource(resource), !IsSupportedSampledDepthResource(resource),
"atomic depth resource was accepted"); "atomic depth resource was accepted");
resource = basic; resource = basic;
resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage; resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::Dynamic;
Require("SampledDepthResource", "dynamic mip rejected", resource.mip_count = 3;
!IsSupportedSampledDepthResource(resource), Require("SampledDepthResource", "dynamic mip read",
"dynamic-storage mip depth resource was accepted"); IsSupportedSampledDepthResource(resource),
"dynamic sampled depth mip resource was rejected");
resource.depth_compare = true;
Require("SampledDepthResource", "dynamic comparison mip read",
IsSupportedSampledDepthResource(resource),
"dynamic comparison depth mip resource was rejected");
resource = basic; resource = basic;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray; resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
Require("SampledDepthResource", "singleton array accepted", Require("SampledDepthResource", "singleton array accepted",
@@ -36946,6 +37153,23 @@ int main(int argc, char **argv) {
RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation()); RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation());
return 0; return 0;
} }
if (argc == 2 && std::strcmp(argv[1], "--image-gather-lod-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, ImageGatherExplicitLod());
RunCase(&vulkan, ImageGatherLodPerLane<0>());
RunCase(&vulkan, ImageGatherLodPerLane<1>());
RunCase(&vulkan, ImageGatherLodPerLane<2>());
RunCase(&vulkan, ImageGatherLodPerLane<3>());
RunCase(&vulkan, ImageGatherLodPerLane<4>());
RunCase(&vulkan, ImageGatherLodPerLane<5>());
RunCase(&vulkan, ImageGatherLodPerLane<6>());
RunCase(&vulkan, ImageGatherLodPerLane<7>());
RunCase(&vulkan, ImageGatherLodPerLane<8>());
RunCase(&vulkan, ImageStoreSingleMipBounds<0>());
RunCase(&vulkan, ImageStoreSingleMipBounds<1>());
RunCase(&vulkan, ImageStoreSingleMipBounds<2>());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--wave64-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--wave64-only") == 0) {
CheckWave64WholeWaveResults(); CheckWave64WholeWaveResults();
VulkanHarness vulkan; VulkanHarness vulkan;
@@ -36982,7 +37206,7 @@ int main(int argc, char **argv) {
RunCase(&vulkan, BranchVccnzUsesWaveMask()); RunCase(&vulkan, BranchVccnzUsesWaveMask());
RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask()); RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask());
RunCase(&vulkan, ImageSampleAndGather()); RunCase(&vulkan, ImageSampleAndGather());
RunCase(&vulkan, ImageGatherLodApproximatesLevelZero()); RunCase(&vulkan, ImageGatherExplicitLod());
RunCase(&vulkan, SharedReturnKeepsSelectedValues()); RunCase(&vulkan, SharedReturnKeepsSelectedValues());
RunCase(&vulkan, SiblingSharedExitKeepsCapturedConditions()); RunCase(&vulkan, SiblingSharedExitKeepsCapturedConditions());
return 0; return 0;
+2 -1
View File
@@ -1309,7 +1309,8 @@ void TestSpirvRequirementsAnalysis() {
block->AppendNewInst(ValueOpcode::ImageQueryLod, block->AppendNewInst(ValueOpcode::ImageQueryLod,
{Value(0u), Value(0u), Value(0u)}); {Value(0u), Value(0u), Value(0u)});
block->AppendNewInst(ValueOpcode::ImageGatherRaw, block->AppendNewInst(ValueOpcode::ImageGatherRaw,
{Value(0u), Value(0u), Value(0u)}); {Value(0u), Value(0u), Value(0u), Value(0u),
Value(0u), Value(0u), Value(0u)});
auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32, auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32,
{Value(0u), Value(true)}); {Value(0u), Value(true)});
shared.SetFlags(MemoryFlags{.index = 0}); shared.SetFlags(MemoryFlags{.index = 0});