diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index a550c6de57..b1671dc958 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -56,6 +56,7 @@ #include "src/ast/type/depth_texture_type.h" #include "src/ast/type/f32_type.h" #include "src/ast/type/pointer_type.h" +#include "src/ast/type/storage_texture_type.h" #include "src/ast/type/texture_type.h" #include "src/ast/type/type.h" #include "src/ast/type/u32_type.h" @@ -2665,12 +2666,8 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { return false; } - if (IsSampledImageAccess(inst.opcode())) { - return EmitSampledImageAccess(inst); - } - if (IsRawImageAccess(inst.opcode())) { - return Fail() << "raw image access is not implemented yet:" - << inst.PrettyPrint(); + if (IsSampledImageAccess(inst.opcode()) || IsRawImageAccess(inst.opcode())) { + return EmitImageAccess(inst); } switch (inst.opcode()) { @@ -2891,6 +2888,21 @@ ast::IdentifierExpression* FunctionEmitter::Swizzle(uint32_t i) { return ast_module_.create(names[i & 3]); } +ast::IdentifierExpression* FunctionEmitter::PrefixSwizzle(uint32_t n) { + switch (n) { + case 1: + return ast_module_.create("x"); + case 2: + return ast_module_.create("xy"); + case 3: + return ast_module_.create("xyz"); + default: + break; + } + Fail() << "invalid swizzle prefix count: " << n; + return nullptr; +} + TypedExpression FunctionEmitter::MakeAccessChain( const spvtools::opt::Instruction& inst) { if (inst.NumInOperands() < 1) { @@ -3626,30 +3638,47 @@ void FunctionEmitter::ApplySourceForInstruction( } } -bool FunctionEmitter::EmitSampledImageAccess( - const spvtools::opt::Instruction& inst) { - auto* result_type = parser_impl_.ConvertType(inst.type_id()); - - // The sampled image operand is always first. - const auto sampled_image_id = inst.GetSingleWordInOperand(0); - const auto* sampler = - parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, false); - const auto* image = - parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, true); - if (!sampler) { - return Fail() << "interal error: couldn't find sampler for " - << inst.PrettyPrint(); - } - if (!image) { - return Fail() << "interal error: couldn't find image for " - << inst.PrettyPrint(); - } - +bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) { + uint32_t arg_index = 0; // The SPIR-V input argument index ast::ExpressionList params; + + const auto image_or_sampled_image_operand_id = + inst.GetSingleWordInOperand(arg_index); + // Form the texture operand. + const auto* image = parser_impl_.GetMemoryObjectDeclarationForHandle( + image_or_sampled_image_operand_id, true); + if (!image) { + return Fail() << "internal error: couldn't find image for " + << inst.PrettyPrint(); + } params.push_back( create(namer_.Name(image->result_id()))); - params.push_back( - create(namer_.Name(sampler->result_id()))); + + if (IsSampledImageAccess(inst.opcode())) { + // Form the sampler operand. + const auto* sampler = parser_impl_.GetMemoryObjectDeclarationForHandle( + image_or_sampled_image_operand_id, false); + if (!sampler) { + return Fail() << "internal error: couldn't find sampler for " + << inst.PrettyPrint(); + } + params.push_back( + create(namer_.Name(sampler->result_id()))); + } + + ast::type::Pointer* texture_ptr_type = + parser_impl_.GetTypeForHandleVar(*image); + if (!texture_ptr_type) { + return Fail(); + } + ast::type::Texture* texture_type = + texture_ptr_type->type()->As(); + if (!texture_type) { + return Fail(); + } + + // We're done with the first SPIR-V operand. Move on to the next. + arg_index++; // Push the coordinates operands. // TODO(dneto): For explicit-Lod variations, we may have to convert from @@ -3662,7 +3691,9 @@ bool FunctionEmitter::EmitSampledImageAccess( return false; } params.insert(params.end(), coords.begin(), coords.end()); - uint32_t arg_index = 2; // Skip over texture and coordinate params + // Skip the coordinates operand. + arg_index++; + const auto num_args = inst.NumInOperands(); std::string builtin_name; @@ -3688,6 +3719,23 @@ bool FunctionEmitter::EmitSampledImageAccess( return Fail() << " image gather is not yet supported"; case SpvOpImageQueryLod: return Fail() << " image query Lod is not yet supported"; + case SpvOpImageWrite: + builtin_name = "textureStore"; + if (arg_index < num_args) { + auto texel = MakeOperand(inst, arg_index); + auto* converted_texel = + ConvertTexelForStorage(inst, texel, texture_type); + if (!converted_texel) { + return false; + } + + params.push_back(converted_texel); + arg_index++; + } else { + return Fail() << "image write is missing a Texel operand: " + << inst.PrettyPrint(); + } + break; default: return Fail() << "internal error: sampled image access"; } @@ -3711,16 +3759,11 @@ bool FunctionEmitter::EmitSampledImageAccess( auto* lod_operand = MakeOperand(inst, arg_index).expr; // When sampling from a depth texture, the Lod operand must be an unsigned // integer. - if (ast::type::Pointer* type = parser_impl_.GetTypeForHandleVar(*image)) { - if (ast::type::Texture* texture_type = - type->type()->As()) { - if (texture_type->Is()) { - // Convert it to an unsigned integer type. - lod_operand = ast_module_.create( - ast_module_.create(), - ast::ExpressionList{lod_operand}); - } - } + if (texture_type->Is()) { + // Convert it to an unsigned integer type. + lod_operand = ast_module_.create( + ast_module_.create(), + ast::ExpressionList{lod_operand}); } params.push_back(lod_operand); image_operands_mask ^= SpvImageOperandsLodMask; @@ -3747,7 +3790,18 @@ bool FunctionEmitter::EmitSampledImageAccess( auto* ident = create(builtin_name); auto* call_expr = create(ident, std::move(params)); - return EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr}); + + if (inst.type_id() != 0) { + // It returns a value. + auto* result_type = parser_impl_.ConvertType(inst.type_id()); + // TODO(dneto): Convert result signedness if needed. crbug.com/tint/382 + EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr}); + } else { + // It's an image write. No value is returned, so make a statement out + // of the call. + AddStatementForInstruction(create(call_expr), inst); + } + return success(); } ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess( @@ -3828,10 +3882,11 @@ ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess( assert(num_axes <= 3); const auto num_coords_required = num_axes + (is_arrayed ? 1 : 0); uint32_t num_coords_supplied = 0; - if (raw_coords.type->Is()) { + if (raw_coords.type->is_float_scalar() || + raw_coords.type->is_integer_scalar()) { num_coords_supplied = 1; - } else if (raw_coords.type->Is()) { - num_coords_supplied = raw_coords.type->As()->size(); + } else if (auto* vec_ty = raw_coords.type->As()) { + num_coords_supplied = vec_ty->size(); } if (num_coords_supplied == 0) { Fail() << "bad or unsupported coordinate type for image access: " @@ -3845,20 +3900,15 @@ ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess( return {}; } - auto prefix_swizzle = [this](uint32_t i) { - const char* prefix_name[] = {"", "x", "xy", "xyz"}; - return ast_module_.create(prefix_name[i & 3]); - }; - ast::ExpressionList result; - // TODO(dneto): Convert component type if needed. + // TODO(dneto): Convert coordinate component type if needed. if (is_arrayed) { // The source must be a vector, because it has enough components and has an // array component. Use a vector swizzle to get the first `num_axes` // components. result.push_back(ast_module_.create( - raw_coords.expr, prefix_swizzle(num_axes))); + raw_coords.expr, PrefixSwizzle(num_axes))); // Now get the array index. ast::Expression* array_index = @@ -3876,12 +3926,97 @@ ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess( // There are more coordinates supplied than needed. So the source type is // a vector. Use a vector swizzle to get the first `num_axes` components. result.push_back(ast_module_.create( - raw_coords.expr, prefix_swizzle(num_axes))); + raw_coords.expr, PrefixSwizzle(num_axes))); } } return result; } +ast::Expression* FunctionEmitter::ConvertTexelForStorage( + const spvtools::opt::Instruction& inst, + TypedExpression texel, + ast::type::Texture* texture_type) { + auto* storage_texture_type = texture_type->As(); + auto* src_type = texel.type; + if (!storage_texture_type) { + Fail() << "writing to other than storage texture: " << inst.PrettyPrint(); + return nullptr; + } + const auto format = storage_texture_type->image_format(); + auto* dest_type = parser_impl_.GetTexelTypeForFormat(format); + if (!dest_type) { + Fail(); + return nullptr; + } + if (src_type == dest_type) { + return texel.expr; + } + + const uint32_t dest_count = + dest_type->is_scalar() ? 1 : dest_type->As()->size(); + if (dest_count == 3) { + Fail() << "3-channel storage textures are not supported: " + << inst.PrettyPrint(); + return nullptr; + } + const uint32_t src_count = + src_type->is_scalar() ? 1 : src_type->As()->size(); + if (src_count < dest_count) { + Fail() << "texel has too few components for storage texture: " << src_count + << " provided but " << dest_count + << " required, in: " << inst.PrettyPrint(); + return nullptr; + } + // If the texel has more components than necessary, then we will ignore the + // higher-numbered components. + auto* texel_prefix = (src_count == dest_count) + ? texel.expr + : ast_module_.create( + texel.expr, PrefixSwizzle(dest_count)); + + if (!(dest_type->is_float_scalar_or_vector() || + dest_type->is_unsigned_scalar_or_vector() || + dest_type->is_signed_scalar_or_vector())) { + Fail() << "invalid destination type for storage texture write: " + << dest_type->type_name(); + return nullptr; + } + if (!(src_type->is_float_scalar_or_vector() || + src_type->is_unsigned_scalar_or_vector() || + src_type->is_signed_scalar_or_vector())) { + Fail() << "invalid texel type for storage texture write: " + << inst.PrettyPrint(); + return nullptr; + } + if (dest_type->is_float_scalar_or_vector() && + !src_type->is_float_scalar_or_vector()) { + Fail() << "can only write float or float vector to a storage image with " + "floating texel format: " + << inst.PrettyPrint(); + return nullptr; + } + if (!dest_type->is_float_scalar_or_vector() && + src_type->is_float_scalar_or_vector()) { + Fail() + << "float or float vector can only be written to a storage image with " + "floating texel format: " + << inst.PrettyPrint(); + return nullptr; + } + + if (dest_type->is_float_scalar_or_vector()) { + return texel_prefix; + } + // The only remaining cases are signed/unsigned source, and signed/unsigned + // destination. + if (dest_type->is_unsigned_scalar_or_vector() == + src_type->is_unsigned_scalar_or_vector()) { + return texel_prefix; + } + // We must do a bitcast conversion. + return ast_module_.create(dest_type, texel_prefix); +} + } // namespace spirv } // namespace reader } // namespace tint diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h index d7a71e7f1f..9a2f226fe3 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -680,6 +680,13 @@ class FunctionEmitter { /// @returns the identifier expression for the @p i'th component ast::IdentifierExpression* Swizzle(uint32_t i); + /// Returns an identifier expression for the swizzle name of the first + /// @p n elements of a vector. Emits an error and returns nullptr if @p n + /// is out of range, i.e. 4 or higher. + /// @param n the number of components in the swizzle + /// @returns the swizzle identifier for the first n elements of a vector + ast::IdentifierExpression* PrefixSwizzle(uint32_t n); + /// Converts SPIR-V image coordinates from an image access instruction /// (e.g. OpImageSampledImplicitLod) into an expression list consisting of /// the texture coordinates, and an integral array index if the texture is @@ -727,10 +734,24 @@ class FunctionEmitter { TypedExpression MakeIntrinsicCall(const spvtools::opt::Instruction& inst); /// Emits a texture builtin function call for a SPIR-V instruction that - /// accesses a sampled image. + /// accesses an image or sampled image. /// @param inst the SPIR-V instruction /// @returns an expression - bool EmitSampledImageAccess(const spvtools::opt::Instruction& inst); + bool EmitImageAccess(const spvtools::opt::Instruction& inst); + + /// Converts the given texel to match the type required for the storage + /// texture with the given type. This can generate a swizzle to retain + /// only the first few components of the texel vector, and maybe a bitcast + /// to convert signedness. Returns an expression, or emits an error and + /// returns nullptr. + /// @param inst the image access instruction (used for diagnostics) + /// @param texel the texel + /// @param texture_type the type of the storage texture + /// @returns the texel, after necessary conversion. + ast::Expression* ConvertTexelForStorage( + const spvtools::opt::Instruction& inst, + TypedExpression texel, + ast::type::Texture* texture_type); /// Returns an expression for an OpSelect, if its operands are scalars /// or vectors. These translate directly to WGSL select. Otherwise, return diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc index 38f5589b39..6ee2251610 100644 --- a/src/reader/spirv/parser_impl.cc +++ b/src/reader/spirv/parser_impl.cc @@ -1601,16 +1601,11 @@ ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id, } } -ast::type::Pointer* ParserImpl::GetTypeForHandleVar( +const spvtools::opt::Instruction* ParserImpl::GetSpirvTypeForHandleVar( const spvtools::opt::Instruction& var) { if (!success()) { return nullptr; } - auto where = handle_type_.find(&var); - if (where != handle_type_.end()) { - return where->second; - } - // The WGSL handle type is determined by looking at information from // several sources: // - the usage of the handle by image access instructions @@ -1650,6 +1645,21 @@ ast::type::Pointer* ParserImpl::GetTypeForHandleVar( << var.PrettyPrint(); return nullptr; } + return raw_handle_type; +} + +ast::type::Pointer* ParserImpl::GetTypeForHandleVar( + const spvtools::opt::Instruction& var) { + auto where = handle_type_.find(&var); + if (where != handle_type_.end()) { + return where->second; + } + + const spvtools::opt::Instruction* raw_handle_type = + GetSpirvTypeForHandleVar(var); + if (!raw_handle_type) { + return nullptr; + } // The variable could be a sampler or image. // Where possible, determine which one it is from the usage inferred @@ -1790,6 +1800,115 @@ ast::type::Pointer* ParserImpl::GetTypeForHandleVar( return result; } +ast::type::Type* ParserImpl::GetComponentTypeForFormat( + ast::type::ImageFormat format) { + switch (format) { + case ast::type::ImageFormat::kR8Uint: + case ast::type::ImageFormat::kR16Uint: + case ast::type::ImageFormat::kRg8Uint: + case ast::type::ImageFormat::kR32Uint: + case ast::type::ImageFormat::kRg16Uint: + case ast::type::ImageFormat::kRgba8Uint: + case ast::type::ImageFormat::kRg32Uint: + case ast::type::ImageFormat::kRgba16Uint: + case ast::type::ImageFormat::kRgba32Uint: + return ast_module_.create(); + + case ast::type::ImageFormat::kR8Sint: + case ast::type::ImageFormat::kR16Sint: + case ast::type::ImageFormat::kRg8Sint: + case ast::type::ImageFormat::kR32Sint: + case ast::type::ImageFormat::kRg16Sint: + case ast::type::ImageFormat::kRgba8Sint: + case ast::type::ImageFormat::kRg32Sint: + case ast::type::ImageFormat::kRgba16Sint: + case ast::type::ImageFormat::kRgba32Sint: + return ast_module_.create(); + + case ast::type::ImageFormat::kR8Unorm: + case ast::type::ImageFormat::kRg8Unorm: + case ast::type::ImageFormat::kRgba8Unorm: + case ast::type::ImageFormat::kRgba8UnormSrgb: + case ast::type::ImageFormat::kBgra8Unorm: + case ast::type::ImageFormat::kBgra8UnormSrgb: + case ast::type::ImageFormat::kRgb10A2Unorm: + case ast::type::ImageFormat::kR8Snorm: + case ast::type::ImageFormat::kRg8Snorm: + case ast::type::ImageFormat::kRgba8Snorm: + case ast::type::ImageFormat::kR16Float: + case ast::type::ImageFormat::kR32Float: + case ast::type::ImageFormat::kRg16Float: + case ast::type::ImageFormat::kRg11B10Float: + case ast::type::ImageFormat::kRg32Float: + case ast::type::ImageFormat::kRgba16Float: + case ast::type::ImageFormat::kRgba32Float: + return ast_module_.create(); + default: + break; + } + Fail() << "unknown format " << int(format); + return nullptr; +} + +ast::type::Type* ParserImpl::GetTexelTypeForFormat( + ast::type::ImageFormat format) { + auto* component_type = GetComponentTypeForFormat(format); + if (!component_type) { + return nullptr; + } + + switch (format) { + case ast::type::ImageFormat::kR16Float: + case ast::type::ImageFormat::kR16Sint: + case ast::type::ImageFormat::kR16Uint: + case ast::type::ImageFormat::kR32Float: + case ast::type::ImageFormat::kR32Sint: + case ast::type::ImageFormat::kR32Uint: + case ast::type::ImageFormat::kR8Sint: + case ast::type::ImageFormat::kR8Snorm: + case ast::type::ImageFormat::kR8Uint: + case ast::type::ImageFormat::kR8Unorm: + // One channel + return component_type; + + case ast::type::ImageFormat::kRg11B10Float: + case ast::type::ImageFormat::kRg16Float: + case ast::type::ImageFormat::kRg16Sint: + case ast::type::ImageFormat::kRg16Uint: + case ast::type::ImageFormat::kRg32Float: + case ast::type::ImageFormat::kRg32Sint: + case ast::type::ImageFormat::kRg32Uint: + case ast::type::ImageFormat::kRg8Sint: + case ast::type::ImageFormat::kRg8Snorm: + case ast::type::ImageFormat::kRg8Uint: + case ast::type::ImageFormat::kRg8Unorm: + // Two channels + return ast_module_.create(component_type, 2); + + case ast::type::ImageFormat::kBgra8Unorm: + case ast::type::ImageFormat::kBgra8UnormSrgb: + case ast::type::ImageFormat::kRgb10A2Unorm: + case ast::type::ImageFormat::kRgba16Float: + case ast::type::ImageFormat::kRgba16Sint: + case ast::type::ImageFormat::kRgba16Uint: + case ast::type::ImageFormat::kRgba32Float: + case ast::type::ImageFormat::kRgba32Sint: + case ast::type::ImageFormat::kRgba32Uint: + case ast::type::ImageFormat::kRgba8Sint: + case ast::type::ImageFormat::kRgba8Snorm: + case ast::type::ImageFormat::kRgba8Uint: + case ast::type::ImageFormat::kRgba8Unorm: + case ast::type::ImageFormat::kRgba8UnormSrgb: + // Four channels + return ast_module_.create(component_type, 4); + + default: + break; + } + Fail() << "unknown format: " << int(format); + return nullptr; +} + bool ParserImpl::RegisterHandleUsage() { if (!success_) { return false; diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h index fbb3e826a9..d3c7a5839a 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -427,6 +427,14 @@ class ParserImpl : Reader { /// @returns the handle usage, or an empty usage object. Usage GetHandleUsage(uint32_t id) const; + /// Returns the SPIR-V type for the sampler or image type for the given + /// variable in UniformConstant storage class. Returns null and emits an + /// error on failure. + /// @param var the OpVariable instruction + /// @returns the Tint AST type for the sampler or texture, or null on error + const spvtools::opt::Instruction* GetSpirvTypeForHandleVar( + const spvtools::opt::Instruction& var); + /// Returns the AST type for the pointer-to-sampler or pointer-to-texture type /// for the given variable in UniformConstant storage class. Returns null and /// emits an error on failure. @@ -436,6 +444,17 @@ class ParserImpl : Reader { ast::type::Pointer* GetTypeForHandleVar( const spvtools::opt::Instruction& var); + /// Returns the channel component type corresponding to the given image + /// format. + /// @param format image texel format + /// @returns the component type, one of f32, i32, u32 + ast::type::Type* GetComponentTypeForFormat(ast::type::ImageFormat format); + + /// Returns texel type corresponding to the given image format. + /// @param format image texel format + /// @returns the texel format + ast::type::Type* GetTexelTypeForFormat(ast::type::ImageFormat format); + /// Returns the SPIR-V instruction with the given ID, or nullptr. /// @param id the SPIR-V result ID /// @returns the instruction, or nullptr on error diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc index a6580e06c4..4f1a61f855 100644 --- a/src/reader/spirv/parser_impl_handle_test.cc +++ b/src/reader/spirv/parser_impl_handle_test.cc @@ -49,17 +49,24 @@ std::string CommonBasicTypes() { %uint = OpTypeInt 32 0 %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 %int_3 = OpConstant %int 3 %int_4 = OpConstant %int 4 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %uint_4 = OpConstant %uint 4 %uint_100 = OpConstant %uint 100 %v2int = OpTypeVector %int 2 - %v2uint = OpTypeVector %uint 2 - %v4uint = OpTypeVector %uint 4 + %v3int = OpTypeVector %int 3 %v4int = OpTypeVector %int 4 + %v2uint = OpTypeVector %uint 2 + %v3uint = OpTypeVector %uint 3 + %v4uint = OpTypeVector %uint 4 %v2float = OpTypeVector %float 2 %v3float = OpTypeVector %float 3 %v4float = OpTypeVector %float 4 @@ -74,10 +81,19 @@ std::string CommonBasicTypes() { %v3float_null = OpConstantNull %v3float %v4float_null = OpConstantNull %v4float + %the_vi12 = OpConstantComposite %v2int %int_1 %int_2 + %the_vi123 = OpConstantComposite %v3int %int_1 %int_2 %int_3 + %the_vi1234 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4 + + %the_vu12 = OpConstantComposite %v2uint %uint_1 %uint_2 + %the_vu123 = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_3 + %the_vu1234 = OpConstantComposite %v4uint %uint_1 %uint_2 %uint_3 %uint_4 + %the_vf12 = OpConstantComposite %v2float %float_1 %float_2 %the_vf123 = OpConstantComposite %v3float %float_1 %float_2 %float_3 %the_vf1234 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4 + %depth = OpConstant %float 0.2 )"; } @@ -219,7 +235,7 @@ TEST_F(SpvParserTest, %20 = OpConstantNull %ptr_f_texture_1d )"; auto p = parser(test::Assemble(assembly)); - ASSERT_TRUE(p->BuildInternalModule()); + ASSERT_TRUE(p->BuildInternalModule()) << assembly; const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false); const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true); @@ -1185,8 +1201,7 @@ INSTANTIATE_TEST_SUITE_P(Images, __storage_texture_write_only_1d_rg32float })"})); -// Test emission of variables when we have sampled image accesses in -// executable code. +// Test emission of variables when we have image accesses in executable code. struct ImageAccessCase { // SPIR-V image type, excluding result ID and opcode @@ -1203,10 +1218,10 @@ inline std::ostream& operator<<(std::ostream& out, const ImageAccessCase& c) { return out; } -using SpvParserTest_DeclHandle_SampledImage = +using SpvParserTest_SampledImageAccessTest = SpvParserTestBase<::testing::TestWithParam>; -TEST_P(SpvParserTest_DeclHandle_SampledImage, Variable) { +TEST_P(SpvParserTest_SampledImageAccessTest, Variable) { const auto assembly = Preamble() + R"( OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft @@ -1281,7 +1296,7 @@ TEST_P(SpvParserTest_RegisterHandleUsage_SampledImage, DISABLED_FunctionParam) { INSTANTIATE_TEST_SUITE_P( DISABLED_ImageGather, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::ValuesIn(std::vector{ // TODO(dneto): OpImageGather // TODO(dneto): OpImageGather with ConstOffset (signed and unsigned) @@ -1291,7 +1306,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( DISABLED_ImageDrefGather, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::ValuesIn(std::vector{ // TODO(dneto): OpImageDrefGather // TODO(dneto): OpImageDrefGather with ConstOffset (signed and @@ -1302,7 +1317,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( ImageSampleImplicitLod, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::Values( // OpImageSampleImplicitLod @@ -1627,7 +1642,7 @@ INSTANTIATE_TEST_SUITE_P( // sampling and depth-refernce sampling. The texture is a depth-texture, // and we use builtins textureSample and textureSampleCompare ImageSampleImplicitLod_BothDrefAndNonDref, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::Values( // OpImageSampleImplicitLod @@ -1705,7 +1720,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( ImageSampleDrefImplicitLod, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::Values( // ImageSampleDrefImplicitLod ImageAccessCase{"%float 2D 0 0 0 1 Unknown", @@ -1866,7 +1881,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( ImageSampleExplicitLod, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::Values( // OpImageSampleExplicitLod - using Lod @@ -2206,7 +2221,7 @@ INSTANTIATE_TEST_SUITE_P( // This corresponds to SPIR-V OpSampleExplicitLod and WGSL textureSampleLevel. INSTANTIATE_TEST_SUITE_P( ImageSampleExplicitLod_DepthTexture, - SpvParserTest_DeclHandle_SampledImage, + SpvParserTest_SampledImageAccessTest, ::testing::ValuesIn(std::vector{ // Test a non-depth case. // (This is already tested above in the ImageSampleExplicitLod suite, @@ -2280,6 +2295,551 @@ INSTANTIATE_TEST_SUITE_P( ) })"}})); +using SpvParserTest_ImageAccessTest = + SpvParserTestBase<::testing::TestWithParam>; + +TEST_P(SpvParserTest_ImageAccessTest, Variable) { + // In this test harness, we only create an image. + const auto assembly = Preamble() + R"( + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpName %f1 "f1" + OpName %vf12 "vf12" + OpName %vf123 "vf123" + OpName %vf1234 "vf1234" + OpName %u1 "u1" + OpName %vu12 "vu12" + OpName %vu123 "vu123" + OpName %vu1234 "vu1234" + OpName %i1 "i1" + OpName %vi12 "vi12" + OpName %vi123 "vi123" + OpName %vi1234 "vi1234" + OpName %offsets2d "offsets2d" + OpDecorate %20 DescriptorSet 2 + OpDecorate %20 Binding 1 +)" + CommonBasicTypes() + + R"( + %im_ty = OpTypeImage )" + + GetParam().spirv_image_type_details + R"( + %ptr_im_ty = OpTypePointer UniformConstant %im_ty + %20 = OpVariable %ptr_im_ty UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %f1 = OpCopyObject %float %float_1 + %vf12 = OpCopyObject %v2float %the_vf12 + %vf123 = OpCopyObject %v3float %the_vf123 + %vf1234 = OpCopyObject %v4float %the_vf1234 + + %i1 = OpCopyObject %int %int_1 + %vi12 = OpCopyObject %v2int %the_vi12 + %vi123 = OpCopyObject %v3int %the_vi123 + %vi1234 = OpCopyObject %v4int %the_vi1234 + + %u1 = OpCopyObject %uint %uint_1 + %vu12 = OpCopyObject %v2uint %the_vu12 + %vu123 = OpCopyObject %v3uint %the_vu123 + %vu1234 = OpCopyObject %v4uint %the_vu1234 + + %value_offset = OpCompositeConstruct %v2int %int_3 %int_4 + %offsets2d = OpCopyObject %v2int %value_offset + %im = OpLoad %im_ty %20 + +)" + GetParam().spirv_image_access + + R"( + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly; + EXPECT_TRUE(p->error().empty()) << p->error(); + const auto module = p->module().to_str(); + EXPECT_THAT(module, HasSubstr(GetParam().var_decl)) + << "DECLARATIONS ARE BAD " << module; + EXPECT_THAT(module, HasSubstr(GetParam().texture_builtin)) + << "TEXTURE BUILTIN IS BAD " << module << assembly; +} + +INSTANTIATE_TEST_SUITE_P( + ImageWrite_OptionalParams, + SpvParserTest_ImageAccessTest, + ::testing::ValuesIn(std::vector{ + // OpImageWrite with no extra params + {"%float 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vu12 %vf1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vf1234} + ) + })"}, + // OpImageWrite with ConstOffset + {"%float 2D 0 0 0 2 Rgba32f", + "OpImageWrite %im %vu12 %vf1234 ConstOffset %offsets2d", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vf1234} + Identifier[not set]{offsets2d} + ) + })"}})); + +INSTANTIATE_TEST_SUITE_P( + // SPIR-V's texel parameter is a 4-element vector with the component + // type matching the sampled type. WGSL's texel parameter might be + // scalar or vector, depending on the number of channels in the texture. + ImageWrite_ConvertTexelOperand_Arity, + SpvParserTest_ImageAccessTest, + ::testing::ValuesIn(std::vector{ + // Source 1 component, dest 1 component + {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %f1", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_r32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{f1} + ) + })"}, + // Source 2 component, dest 1 component + {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %vf12", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_r32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + MemberAccessor[not set]{ + Identifier[not set]{vf12} + Identifier[not set]{x} + } + ) + })"}, + // Source 3 component, dest 1 component + {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %vf123", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_r32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + MemberAccessor[not set]{ + Identifier[not set]{vf123} + Identifier[not set]{x} + } + ) + })"}, + // Source 4 component, dest 1 component + {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %vf1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_r32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + MemberAccessor[not set]{ + Identifier[not set]{vf1234} + Identifier[not set]{x} + } + ) + })"}, + // Source 2 component, dest 2 component + {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vu12 %vf12", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rg32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vf12} + ) + })"}, + // Source 3 component, dest 2 component + {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vu12 %vf123", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rg32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + MemberAccessor[not set]{ + Identifier[not set]{vf123} + Identifier[not set]{xy} + } + ) + })"}, + // Source 4 component, dest 2 component + {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vu12 %vf1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rg32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + MemberAccessor[not set]{ + Identifier[not set]{vf1234} + Identifier[not set]{xy} + } + ) + })"}, + // WGSL does not support 3-component storage textures. + // Source 4 component, dest 4 component + {"%float 2D 0 0 0 2 Rgba32f", "OpImageWrite %im %vu12 %vf1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32float + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vf1234} + ) + })"}})); + +TEST_F(SpvParserTest, ImageWrite_TooFewSrcTexelComponents_1_vs_4) { + const auto assembly = Preamble() + R"( + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpName %f1 "f1" + OpName %coords12 "coords12" + OpDecorate %20 DescriptorSet 2 + OpDecorate %20 Binding 1 +)" + CommonBasicTypes() + + R"( + %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgba32f + %ptr_im_ty = OpTypePointer UniformConstant %im_ty + + %20 = OpVariable %ptr_im_ty UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %f1 = OpCopyObject %float %float_1 + + %coords12 = OpCopyObject %v2float %the_vf12 + + %im = OpLoad %im_ty %20 + OpImageWrite %im %coords12 %f1 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + EXPECT_FALSE(p->BuildAndParseInternalModule()); + EXPECT_THAT(p->error(), + Eq("texel has too few components for storage texture: 1 provided " + "but 4 required, in: OpImageWrite %52 %3 %2")) + << p->error(); +} + +TEST_F(SpvParserTest, ImageWrite_ThreeComponentStorageTexture_IsError) { + // SPIR-V doesn't allow a 3-element storage texture format. + const auto assembly = Preamble() + R"( + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpName %vf123 "vf123" + OpName %coords12 "coords12" + OpDecorate %20 DescriptorSet 2 + OpDecorate %20 Binding 1 +)" + CommonBasicTypes() + + R"( + %im_ty = OpTypeImage %void 2D 0 0 0 2 Rgb32f + %ptr_im_ty = OpTypePointer UniformConstant %im_ty + + %20 = OpVariable %ptr_im_ty UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %vf123 = OpCopyObject %v3float %the_vf123 + + %coords12 = OpCopyObject %v2float %the_vf12 + + %im = OpLoad %im_ty %20 + OpImageWrite %im %coords12 %vf123 + OpReturn + OpFunctionEnd + )"; + auto error = test::AssembleFailure(assembly); + EXPECT_THAT(error, HasSubstr("Invalid image format 'Rgb32f'")); +} + +TEST_F(SpvParserTest, ImageWrite_FloatDest_IntegralSrc_IsError) { + const auto assembly = Preamble() + R"( + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpName %coords12 "coords12" + OpDecorate %20 DescriptorSet 2 + OpDecorate %20 Binding 1 +)" + CommonBasicTypes() + + R"( + %im_ty = OpTypeImage %void 2D 0 0 0 2 R32f + %ptr_im_ty = OpTypePointer UniformConstant %im_ty + + %20 = OpVariable %ptr_im_ty UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %f1 = OpCopyObject %float %float_1 + + %coords12 = OpCopyObject %v2float %the_vf12 + + %im = OpLoad %im_ty %20 + OpImageWrite %im %coords12 %uint_0 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + EXPECT_FALSE(p->BuildAndParseInternalModule()); + EXPECT_THAT(p->error(), + Eq("can only write float or float vector to a storage image with " + "floating texel format: OpImageWrite %52 %2 %13")) + << p->error(); +} + +TEST_F(SpvParserTest, ImageWrite_IntegralDest_FloatSrc_IsError) { + const auto assembly = Preamble() + R"( + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpName %coords12 "coords12" + OpDecorate %20 DescriptorSet 2 + OpDecorate %20 Binding 1 +)" + CommonBasicTypes() + + R"( + %im_ty = OpTypeImage %void 2D 0 0 0 2 R32ui + %ptr_im_ty = OpTypePointer UniformConstant %im_ty + + %20 = OpVariable %ptr_im_ty UniformConstant + + %main = OpFunction %void None %voidfn + %entry = OpLabel + + %f1 = OpCopyObject %float %float_1 + + %coords12 = OpCopyObject %v2float %f1 + + %im = OpLoad %im_ty %20 + OpImageWrite %im %coords12 %f1 + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + EXPECT_FALSE(p->BuildAndParseInternalModule()); + EXPECT_THAT(p->error(), + Eq("float or float vector can only be written to a storage image " + "with floating texel format: OpImageWrite %52 %2 %51")) + << p->error(); +} + +INSTANTIATE_TEST_SUITE_P( + // Convert texel values when the sampled type of the texture is of the + // wrong signedness: + // unsigned int channel type -> signed int sampled texture + // signed int channel type -> unsigned int sampled texture + // (It is already a SPIR-V validation rule that floating point texels + // must already be used with textures of floating point sampled types) + ImageWrite_ConvertTexelOperand_Signedness, + SpvParserTest_ImageAccessTest, + ::testing::ValuesIn(std::vector{ + // Sampled type is unsigned int, texel is unsigned int + {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vu12 %vu1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32uint + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vu1234} + ) + })"}, + // Sampled type is unsigned int, texel is signed int + {"%uint 2D 0 0 0 2 Rgba32ui", "OpImageWrite %im %vu12 %vi1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32uint + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Bitcast[not set]<__vec_4__u32>{ + Identifier[not set]{vi1234} + } + ) + })"}, + // Sampled type is signed int, texel is unsigned int + {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vu12 %vu1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32sint + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Bitcast[not set]<__vec_4__i32>{ + Identifier[not set]{vu1234} + } + ) + })"}, + // Sampled type is signed int, texel is signed int + {"%int 2D 0 0 0 2 Rgba32i", "OpImageWrite %im %vu12 %vi1234", + R"( + DecoratedVariable{ + Decorations{ + SetDecoration{2} + BindingDecoration{1} + } + x_20 + uniform_constant + __storage_texture_write_only_2d_rgba32sint + })", + R"(Call[not set]{ + Identifier[not set]{textureStore} + ( + Identifier[not set]{x_20} + Identifier[not set]{vu12} + Identifier[not set]{vi1234} + ) + })"}})); + +INSTANTIATE_TEST_SUITE_P( + // The SPIR-V result type could be integral but of different signedness + // than the sampled texel type. In these cases the result should be + // converted to match the signedness of the SPIR-V result type. This + // affects any instruction that yields texel values. + DISABLED_ImageAccess_ConvertResultSignedness, + SpvParserTest_ImageAccessTest, + ::testing::ValuesIn(std::vector + // OpImageRead + // OpImageFetch + // OpImageGather + // OpImageSampleExplicitLod + // OpImageSampleImplicitLod + // In WGSL, depth-reference sampling only yields + // floating point results in WGSL. + {})); + struct ImageCoordsCase { // SPIR-V image type, excluding result ID and opcode std::string spirv_image_type_details; @@ -2626,7 +3186,7 @@ INSTANTIATE_TEST_SUITE_P(BadInstructions, {"%float 1D 0 0 0 1 Unknown", "%50 = OpCopyObject %float %float_1", "internal error: couldn't find image for " - "%50 = OpCopyObject %9 %28", + "%50 = OpCopyObject %9 %36", {}}, {"%float 1D 0 0 0 1 Unknown", "OpStore %float_var %float_1", @@ -2645,36 +3205,36 @@ INSTANTIATE_TEST_SUITE_P( "%result = OpImageSampleImplicitLod " // bad type for coordinate: not a number "%v4float %sampled_image %float_var", - "bad or unsupported coordinate type for image access: %50 = " - "OpImageSampleImplicitLod %26 %49 %1", + "bad or unsupported coordinate type for image access: %63 = " + "OpImageSampleImplicitLod %34 %62 %1", {}}, {"%float 1D 0 1 0 1 Unknown", // 1DArray "%result = OpImageSampleImplicitLod " // 1 component, but need 2 "%v4float %sampled_image %f1", "image access required 2 coordinate components, but only 1 provided, " - "in: %50 = OpImageSampleImplicitLod %26 %49 %3", + "in: %63 = OpImageSampleImplicitLod %34 %62 %3", {}}, {"%float 2D 0 0 0 1 Unknown", // 2D "%result = OpImageSampleImplicitLod " // 1 component, but need 2 "%v4float %sampled_image %f1", "image access required 2 coordinate components, but only 1 provided, " - "in: %50 = OpImageSampleImplicitLod %26 %49 %3", + "in: %63 = OpImageSampleImplicitLod %34 %62 %3", {}}, {"%float 2D 0 1 0 1 Unknown", // 2DArray "%result = OpImageSampleImplicitLod " // 2 component, but need 3 "%v4float %sampled_image %vf12", "image access required 3 coordinate components, but only 2 provided, " - "in: %50 = OpImageSampleImplicitLod %26 %49 %4", + "in: %63 = OpImageSampleImplicitLod %34 %62 %4", {}}, {"%float 3D 0 0 0 1 Unknown", // 3D "%result = OpImageSampleImplicitLod " // 2 components, but need 3 "%v4float %sampled_image %vf12", "image access required 3 coordinate components, but only 2 provided, " - "in: %50 = OpImageSampleImplicitLod %26 %49 %4", + "in: %63 = OpImageSampleImplicitLod %34 %62 %4", {}}, }));