shader: validate indirect image operands at each typed access

This commit is contained in:
nmzik
2026-10-12 20:12:09 +02:00
parent 0e9c77b0a2
commit f01a3750d4
6 changed files with 224 additions and 38 deletions
@@ -61,10 +61,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
}
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
ImageSampleLayout layout;
uint32_t cursor = 0;
const auto& info = ImageDimensionInfoFor(mem.image_dimension);
const auto& info = ImageDimensionInfoFor(dimension);
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
@@ -80,6 +80,48 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
return layout;
}
void ValidateIndirectImageOperands(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
const auto& memory = ctx.Memory(inst);
const auto& images = ctx.state.program.info.images;
const auto& root = images.at(memory.resource);
if (root.indirect_root != memory.resource) return;
if (op != IR::ValueOpcode::ImageSampleRaw &&
op != IR::ValueOpcode::ImageGatherRaw && op != IR::ValueOpcode::ImageRead) {
ctx.Fail(inst, "requires an unsupported indirect image operation");
}
const auto& native = ImageDimensionInfoFor(memory.image_dimension);
const auto native_layout = Layout(memory, memory.image_dimension);
const bool sampling = op == IR::ValueOpcode::ImageSampleRaw ||
op == IR::ValueOpcode::ImageGatherRaw;
const auto validate = [&](const IR::ImageResource& image) {
const auto& candidate = ImageDimensionInfoFor(image.dimension);
// Sample/fetch ignore the native array slice for a non-array T# while
// keeping the original positions of later operands.
const bool projected_2d = memory.image_dimension == ImageDimension::Dim2DArray &&
image.dimension == ImageDimension::Dim2D;
bool compatible = projected_2d ||
(candidate.coordinate_components == native.coordinate_components &&
candidate.multisampled == native.multisampled);
if (sampling) {
compatible &= candidate.multisampled == 0u && native.multisampled == 0u;
compatible &= projected_2d || Layout(memory, image.dimension) == native_layout;
if (HasFlag(memory, Decoder::ImageSampleFlagDerivative)) {
compatible &= candidate.spatial_components == native.spatial_components;
}
if (op == IR::ValueOpcode::ImageGatherRaw ||
HasFlag(memory, Decoder::ImageSampleFlagCompare)) {
compatible &= image.dimension != ImageDimension::Dim3D;
}
}
if (!compatible) ctx.Fail(inst, "has incompatible image operand layout");
};
validate(root);
for (const auto resource: root.indirect_resources) {
if (resource != memory.resource) validate(images.at(resource));
}
}
uint32_t ZeroF32(EmitterState& state) {
return ConstantF32(state, 0);
}
@@ -736,6 +778,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto& mem = ctx.Memory(inst);
const auto image_arg = inst.Arg(0);
ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource);
ValidateIndirectImageOperands(ctx, inst);
const auto& image = state.program.info.images.at(mem.resource);
const auto* address = ctx.ImageAddress(inst.Arg(image_info.needs_sampler ? 2 : 1));
if (op == IR::ValueOpcode::ImageQueryDimensions) {
@@ -817,7 +860,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
const auto dimension = image.dimension;
const auto& dimension_info = ImageDimensionInfoFor(dimension);
const auto layout = Layout(mem);
const auto layout = Layout(mem, mem.image_dimension);
const auto numeric_class = image.numeric_class;
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
IR::Value offset_value;
@@ -272,6 +272,8 @@ struct ImageSampleLayout {
uint32_t lod = NoImageComponent;
uint32_t grad_x = NoImageComponent;
uint32_t grad_y = NoImageComponent;
bool operator==(const ImageSampleLayout&) const = default;
};
struct F32Class {
@@ -621,10 +621,6 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
return SpecializationFail("indirect image specialization has no typed candidate");
}
const auto& image_class = specialization.images[exemplar];
const auto is_2d = [](Decoder::ImageDimension dimension) {
return dimension == Decoder::ImageDimension::Dim2D ||
dimension == Decoder::ImageDimension::Dim2DArray;
};
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
auto& image = specialization.images[candidate];
if (image.indirect_root != root_index) {
@@ -638,10 +634,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
image.shader_swizzle = image_class.shader_swizzle;
image.cube = image_class.cube;
}
const bool same_coordinates = image.dimension == image_class.dimension &&
image.cube == image_class.cube;
if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
image.mip_count != image_class.mip_count ||
if (image.mip_count != image_class.mip_count ||
image.conversion_format != image_class.conversion_format ||
image.shader_swizzle != image_class.shader_swizzle) {
return SpecializationFail(
@@ -1414,10 +1407,6 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
memory.sampler = sampler_plan.mapping[memory.sampler][type];
EXIT_IF(memory.sampler == UINT32_MAX);
}
EXIT_IF(image.indirect_root == memory.resource &&
inst.GetOpcode() != ValueOpcode::ImageSampleRaw &&
inst.GetOpcode() != ValueOpcode::ImageGatherRaw &&
inst.GetOpcode() != ValueOpcode::ImageRead);
}
}
for (auto& memory: memory_info) {