mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 20:36:58 +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) {
|
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)}));
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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});
|
||||||
|
|||||||
Reference in New Issue
Block a user