mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 12:56:53 +02:00
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:
@@ -90,8 +90,11 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
|
||||
return false;
|
||||
}
|
||||
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic;
|
||||
return ((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
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,
|
||||
@@ -114,7 +117,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
resource.mip_count == 1u) ||
|
||||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage &&
|
||||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic &&
|
||||
resource.mip_count != 0u)) &&
|
||||
resource.written &&
|
||||
(!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;
|
||||
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
|
||||
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
|
||||
? 1u
|
||||
: static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
@@ -891,7 +891,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto& binding = images[i];
|
||||
binding.mip_views.clear();
|
||||
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 ||
|
||||
resource.mip_count != binding.desc.view_info.level_count);
|
||||
binding.mip_views.reserve(resource.mip_count);
|
||||
@@ -899,6 +899,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto desc = binding.desc;
|
||||
desc.view_info.base_level += mip;
|
||||
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.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");
|
||||
}
|
||||
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;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
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) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
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");
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -183,10 +174,10 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
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 = LoadSampledImageDescriptor(state, resource);
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
EXIT_IF(mip >= image.mip_count);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
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(
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
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,
|
||||
@@ -214,46 +205,15 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
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;
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip),
|
||||
coord, texel);
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index), coord, texel);
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
return;
|
||||
}
|
||||
if (image.mip_count == 0u) {
|
||||
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);
|
||||
EmitImageMipSwitch(state, mip_lod, image.mip_count, 0, EmitWrite);
|
||||
}
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -86,6 +84,46 @@ uint32_t ZeroF32(EmitterState& state) {
|
||||
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) {
|
||||
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 auto dimension = ctx.state.program.info.images.at(mem.resource).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();
|
||||
if (info.multisampled != 0u) {
|
||||
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) {
|
||||
auto& state = ctx.state;
|
||||
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();
|
||||
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image,
|
||||
ConstantU32(state, 0));
|
||||
@@ -446,7 +484,8 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
|
||||
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 scalar_type = ImageScalarType(state, numeric_class);
|
||||
const auto component =
|
||||
@@ -558,7 +597,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
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();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
@@ -588,7 +628,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
EmitValueOrDefaultIfCondition(
|
||||
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 coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
@@ -612,7 +652,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto dimension = image.dimension;
|
||||
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
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)
|
||||
: 0u;
|
||||
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) {
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
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 (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words;
|
||||
const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
|
||||
: numeric_class;
|
||||
const auto result_type = ImageVectorType(state, result_numeric_class, 4);
|
||||
uint32_t component_or_dref;
|
||||
if (dref) {
|
||||
auto dref_value = ZeroF32(state);
|
||||
if (layout.dref != NoImageComponent) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
words = {spv::OpImageDrefGather,
|
||||
TypeF32Vector(state, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
dref_value};
|
||||
component_or_dref = layout.dref != NoImageComponent
|
||||
? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state);
|
||||
} else {
|
||||
uint32_t component = 0;
|
||||
if (ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid) {
|
||||
component = ImageGatherComponent(mem.dmask);
|
||||
}
|
||||
words = {spv::OpImageGather,
|
||||
ImageVectorType(state, numeric_class, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
ConstantU32(state, component)};
|
||||
component_or_dref = ConstantU32(
|
||||
state, ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask) : 0u);
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
uint32_t offset = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
words.push_back(spv::ImageOperandsConstOffsetsMask);
|
||||
words.push_back(HorizontalOffsets(state, dimension));
|
||||
operand_mask = spv::ImageOperandsConstOffsetsMask;
|
||||
offset = HorizontalOffsets(state, dimension);
|
||||
} else if (layout.offset != NoImageComponent) {
|
||||
words.push_back(spv::ImageOperandsOffsetMask);
|
||||
words.push_back(PackedOffset(ctx, mem, *address, layout, dimension));
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
auto result_numeric_class = numeric_class;
|
||||
if (dref) {
|
||||
result_numeric_class = Prospero::TextureNumericClass::Float;
|
||||
operand_mask = spv::ImageOperandsOffsetMask;
|
||||
offset = PackedOffset(ctx, mem, *address, layout, dimension);
|
||||
}
|
||||
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),
|
||||
result_numeric_class, false, mem, true));
|
||||
return;
|
||||
@@ -738,13 +775,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operand_mask |= spv::ImageOperandsBiasMask;
|
||||
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& candidate = state.program.info.images[resource];
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler_id);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
@@ -874,7 +912,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
spv::OpTypePointer, spv::StorageClassImage,
|
||||
image.atomic64 ? TypeScalarU64(state) : TypeU32(state));
|
||||
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
ImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
ConstantU32(state, 0));
|
||||
if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <map>
|
||||
#include <spirv/unified1/GLSL.std.450.h>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -314,13 +315,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image);
|
||||
|
||||
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 MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler);
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource);
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id,
|
||||
uint32_t mip = 0);
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
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);
|
||||
|
||||
// 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>
|
||||
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
|
||||
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 sampler = GetSamplerResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageGatherRaw, {resource, sampler, address},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
const bool has_lod = (memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u;
|
||||
// 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++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)}));
|
||||
|
||||
@@ -114,7 +114,7 @@ struct BufferResource {
|
||||
bool operator==(const BufferResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class ImageMipMode { None, DynamicStorage };
|
||||
enum class ImageMipMode { None, Dynamic };
|
||||
|
||||
constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu;
|
||||
|
||||
@@ -151,6 +151,7 @@ struct SamplerResource {
|
||||
bool force_point_filtering = false;
|
||||
bool depth_compare = false;
|
||||
bool integer_border = false;
|
||||
bool gather_lod = false;
|
||||
|
||||
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(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
|
||||
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(ImageAtomicSwap32, 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);
|
||||
}
|
||||
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;
|
||||
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,
|
||||
|
||||
@@ -124,8 +124,8 @@ bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
Prospero::ImageType::kCube;
|
||||
}
|
||||
|
||||
uint32_t StorageMipCount(const ImageResource& image, const DescriptorValue& descriptor) {
|
||||
if (image.mip_mode != ImageMipMode::DynamicStorage || NullImageDescriptor(descriptor)) {
|
||||
uint32_t ImageMipCount(const ImageResource& image, const DescriptorValue& descriptor) {
|
||||
if (image.mip_mode != ImageMipMode::Dynamic || NullImageDescriptor(descriptor)) {
|
||||
return 1;
|
||||
}
|
||||
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)) {
|
||||
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) {
|
||||
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)) {
|
||||
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])) {
|
||||
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());
|
||||
return BuildResourceSpecialization(program, snapshot, specialization);
|
||||
|
||||
@@ -1727,9 +1727,11 @@ private:
|
||||
const auto resource_class = ImageOpcodeInfoOf(op).resource_class;
|
||||
const bool atomic64 = ImageOpcodeInfoOf(op).access == ImageAccess::Atomic &&
|
||||
memory.data_bits == 64u;
|
||||
const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip
|
||||
? ImageMipMode::DynamicStorage
|
||||
: ImageMipMode::None;
|
||||
const bool dynamic_mip =
|
||||
(resource_class == ImageResourceClass::Storage && memory.image_has_mip) ||
|
||||
(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;
|
||||
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
||||
auto& image = m_info.images[i];
|
||||
@@ -1939,6 +1941,9 @@ private:
|
||||
if (sampler == UINT32_MAX) {
|
||||
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);
|
||||
AddSampledPair(resource, sampler, flags.pc);
|
||||
}
|
||||
|
||||
@@ -2032,6 +2032,58 @@ void TestFmaskLoadSpecialization() {
|
||||
"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() {
|
||||
Fixture fixture;
|
||||
std::array<Value, 8> image_words;
|
||||
@@ -2067,7 +2119,7 @@ void TestDynamicStorageMipTracking() {
|
||||
const auto &images = fixture.program.info.images;
|
||||
Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None &&
|
||||
images[0].mip_count == 1 &&
|
||||
images[1].mip_mode == ImageMipMode::DynamicStorage &&
|
||||
images[1].mip_mode == ImageMipMode::Dynamic &&
|
||||
images[1].mip_count == 1,
|
||||
"storage mip writes did not share one dynamic logical resource");
|
||||
Check(plain.first.Instruction()->Flags<uint32_t>() == 0 &&
|
||||
@@ -3412,6 +3464,7 @@ int main() {
|
||||
Run("images and samplers", TestImagesSamplersAndAliases);
|
||||
Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch);
|
||||
Run("FMASK load specialization", TestFmaskLoadSpecialization);
|
||||
Run("gather LOD sampler validation", TestGatherLodSamplerValidation);
|
||||
Run("dynamic storage mips", TestDynamicStorageMipTracking);
|
||||
Run("invariant indirect images", TestInvariantIndirectImageMaterialization);
|
||||
Run("guarded direct image table", TestGuardedDirectImageTable);
|
||||
|
||||
@@ -1175,6 +1175,7 @@ struct TestCase {
|
||||
vk::ImageType sampled_image_type = vk::ImageType::e2D;
|
||||
vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D;
|
||||
u32 sampled_image_layers = 1;
|
||||
u32 sampled_image_view_base_mip = 0;
|
||||
u32 sampled_image_view_base_layer = 0;
|
||||
u32 sampled_image_view_layers = 0;
|
||||
std::vector<u32> storage_image_rgba;
|
||||
@@ -1207,7 +1208,7 @@ struct TestCase {
|
||||
std::vector<u32> expected_gds;
|
||||
std::vector<std::pair<std::string, size_t>> decoded_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;
|
||||
};
|
||||
|
||||
@@ -1517,28 +1518,26 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
||||
text + " count=" + std::to_string(actual) +
|
||||
", 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(
|
||||
result.program.info.images, [](const auto &resource) {
|
||||
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->mip_count == test.expected_storage_mip_descriptors,
|
||||
image->mip_count == test.expected_mip_descriptors,
|
||||
"runtime descriptor range did not specialize the mip count");
|
||||
const auto resource =
|
||||
static_cast<u32>(image - result.program.info.images.begin());
|
||||
const auto binding = std::ranges::find_if(
|
||||
result.program.bindings.descriptors, [&](const auto &group) {
|
||||
return std::ranges::find(group.resources, resource) !=
|
||||
group.resources.end();
|
||||
});
|
||||
Require(test.name, "dynamic storage mip binding",
|
||||
binding != result.program.bindings.descriptors.end() &&
|
||||
const auto kind = ShaderRecompiler::IR::DescriptorBindingForImage(*image);
|
||||
const auto *binding = kind ? ShaderRecompiler::IR::FindBinding(
|
||||
result.program.bindings, *kind) : nullptr;
|
||||
Require(test.name, "dynamic mip binding",
|
||||
binding != nullptr &&
|
||||
std::ranges::count(binding->resources, resource) ==
|
||||
test.expected_storage_mip_descriptors,
|
||||
"dynamic storage image did not receive one descriptor per mip");
|
||||
test.expected_mip_descriptors,
|
||||
"dynamic image did not receive one descriptor per mip");
|
||||
}
|
||||
if (test.expand_shader_data_storage) {
|
||||
auto &block = *result.program.blocks.front();
|
||||
@@ -11225,7 +11224,7 @@ public:
|
||||
"IMAGE_STORE expanded an inaccessible mip and shifted physical storage");
|
||||
auto mipped_storage_resource = storage_resource;
|
||||
mipped_storage_resource.mip_mode =
|
||||
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
ShaderRecompiler::IR::ImageMipMode::Dynamic;
|
||||
mipped_storage_resource.mip_count = 3;
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
ExpectFatal("MipViewPhysicalLayoutChange", [&] {
|
||||
@@ -11514,6 +11513,33 @@ public:
|
||||
views[2] != views[3] && views[1] == views.back(),
|
||||
"different texture clamps aliased or identical clamped views "
|
||||
"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);
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
}
|
||||
@@ -13786,7 +13812,8 @@ public:
|
||||
u32 layers, u32 view_base_layer = 0,
|
||||
u32 view_layers = 0,
|
||||
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;
|
||||
ret.format = format;
|
||||
ret.width = width;
|
||||
@@ -13845,8 +13872,10 @@ public:
|
||||
view_info.viewType = view_type;
|
||||
view_info.format = view_format == vk::Format::eUndefined ? format : view_format;
|
||||
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
view_info.subresourceRange.baseMipLevel = 0;
|
||||
view_info.subresourceRange.levelCount = ret.mip_levels;
|
||||
Require(shader_name, "dispatch", view_base_mip < 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,
|
||||
"image view base layer is out of bounds");
|
||||
if (view_layers == 0) {
|
||||
@@ -14134,6 +14163,7 @@ public:
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
std::vector<vk::DescriptorBufferInfo> buffer_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_uint_infos;
|
||||
std::vector<vk::DescriptorImageInfo> storage_atomic_infos;
|
||||
@@ -14271,9 +14301,32 @@ public:
|
||||
"sampled image descriptor requested but no sampled image was "
|
||||
"provided");
|
||||
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.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{};
|
||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
@@ -14428,6 +14481,9 @@ public:
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
EndSubmitAndFree(test.name, "dispatch", cmd);
|
||||
for (const auto view : sampled_mip_views) {
|
||||
m_device.destroyImageView(view, nullptr);
|
||||
}
|
||||
if (flattened_buffer.buffer != nullptr) {
|
||||
DestroyBuffer(&flattened_buffer);
|
||||
}
|
||||
@@ -17346,7 +17402,8 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
||||
sampled_dwords_per_pixel, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
test.sampled_image_type, test.sampled_image_view_type,
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
@@ -29981,34 +30039,135 @@ TestCase ImageCubeGradientsPreserveDerivatives() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageGatherLodApproximatesLevelZero() {
|
||||
TestCase ImageGatherExplicitLod() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code;
|
||||
AppendVMovLiteral(&code, 20, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 21, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 22, 0x3f800000u);
|
||||
code.push_back(EncodeMimg0(0x44, 0x1));
|
||||
code.push_back(EncodeMimg1(0, 20));
|
||||
code.push_back(EncodeMimg0(0x24, 0x1));
|
||||
code.push_back(EncodeMimg1(4, 20));
|
||||
// PPSA29343 uses NSA coordinates v81/v84 with its explicit LOD in v78.
|
||||
AppendVMovLiteral(&code, 81, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 84, 0x3f000000u);
|
||||
AppendVMovLiteral(&code, 78, 0x3f800000u);
|
||||
code.push_back(EncodeMimg0(0x44, 0x1, 1));
|
||||
code.push_back(EncodeMimg1(9, 81, 0, 2));
|
||||
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++) {
|
||||
AppendStoreVgpr(&code, i, i);
|
||||
AppendStoreVgpr(&code, i + 9, i);
|
||||
}
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "ImageGatherLodApproximatesLevelZero";
|
||||
test.name = "ImageGatherExplicitLod";
|
||||
test.code = std::move(code);
|
||||
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_L, O::IMAGE_SAMPLE,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
// Explicit sampling proves mip 1 is accessible; the gather must still read mip 0.
|
||||
test.expected = {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x41000000u};
|
||||
// A gather's single-mip descriptors must not replace the ordinary sample's
|
||||
// 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),
|
||||
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"};
|
||||
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() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -30441,11 +30600,42 @@ TestCase ImageStoreMipSelectsPpsa01340Descriptor() {
|
||||
std::copy(descriptor.begin(), descriptor.end(), test.user_data.begin());
|
||||
test.has_user_data = true;
|
||||
test.compile_only = true;
|
||||
test.expected_storage_mip_descriptors = 3;
|
||||
test.expected_mip_descriptors = 3;
|
||||
test.required_spirv = {"OpSwitch", "OpImageWrite"};
|
||||
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() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -32009,7 +32199,16 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageGetResinfoDmaskMipLevels);
|
||||
AddCase(ImageSampleAndGather);
|
||||
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(ImageSampleA16SamplerCoordsOnGpu);
|
||||
AddCase(ImageSampleOpcodeAliasUsesNormalCoords);
|
||||
@@ -32019,6 +32218,9 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageStoreVariants);
|
||||
AddCase(ImageD16StoreUnpacksHalfPairs);
|
||||
AddCase(ImageStoreMipSelectsPpsa01340Descriptor);
|
||||
AddCase(ImageStoreSingleMipBounds<0>);
|
||||
AddCase(ImageStoreSingleMipBounds<1>);
|
||||
AddCase(ImageStoreSingleMipBounds<2>);
|
||||
AddCase(ImageStoreRgbOneUsesInverseSwizzle);
|
||||
AddCase(ImageStoreDuplicateSelectorUsesInverseSwizzle);
|
||||
AddCase(ImageStoreBgraUsesInverseSwizzle);
|
||||
@@ -33060,7 +33262,7 @@ void CheckSampledColorViews() {
|
||||
"atomic uint storage resource was rejected");
|
||||
storage_resource.atomic = false;
|
||||
storage_resource.mip_mode =
|
||||
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
ShaderRecompiler::IR::ImageMipMode::Dynamic;
|
||||
storage_resource.mip_count = 3;
|
||||
Require("SampledColorViews", "dynamic storage mip resource",
|
||||
IsSupportedStorageImageResource(storage_resource),
|
||||
@@ -33133,10 +33335,15 @@ void CheckSampledDepthResource() {
|
||||
!IsSupportedSampledDepthResource(resource),
|
||||
"atomic depth resource was accepted");
|
||||
resource = basic;
|
||||
resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
Require("SampledDepthResource", "dynamic mip rejected",
|
||||
!IsSupportedSampledDepthResource(resource),
|
||||
"dynamic-storage mip depth resource was accepted");
|
||||
resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::Dynamic;
|
||||
resource.mip_count = 3;
|
||||
Require("SampledDepthResource", "dynamic mip read",
|
||||
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.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
Require("SampledDepthResource", "singleton array accepted",
|
||||
@@ -36946,6 +37153,23 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation());
|
||||
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) {
|
||||
CheckWave64WholeWaveResults();
|
||||
VulkanHarness vulkan;
|
||||
@@ -36982,7 +37206,7 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, BranchVccnzUsesWaveMask());
|
||||
RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask());
|
||||
RunCase(&vulkan, ImageSampleAndGather());
|
||||
RunCase(&vulkan, ImageGatherLodApproximatesLevelZero());
|
||||
RunCase(&vulkan, ImageGatherExplicitLod());
|
||||
RunCase(&vulkan, SharedReturnKeepsSelectedValues());
|
||||
RunCase(&vulkan, SiblingSharedExitKeepsCapturedConditions());
|
||||
return 0;
|
||||
|
||||
@@ -1309,7 +1309,8 @@ void TestSpirvRequirementsAnalysis() {
|
||||
block->AppendNewInst(ValueOpcode::ImageQueryLod,
|
||||
{Value(0u), Value(0u), Value(0u)});
|
||||
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,
|
||||
{Value(0u), Value(true)});
|
||||
shared.SetFlags(MemoryFlags{.index = 0});
|
||||
|
||||
Reference in New Issue
Block a user