spirv-reader: support textureStore

Fixes: tint:381
Change-Id: I401cebcd94b92365037228e0bd50b67bc8d12ab8
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/34424
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Auto-Submit: David Neto <dneto@google.com>
This commit is contained in:
David Neto 2020-12-01 20:33:07 +00:00 committed by Commit Bot service account
parent 2478ae78bb
commit 91ad3961df
5 changed files with 933 additions and 79 deletions

View File

@ -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<ast::IdentifierExpression>(names[i & 3]);
}
ast::IdentifierExpression* FunctionEmitter::PrefixSwizzle(uint32_t n) {
switch (n) {
case 1:
return ast_module_.create<ast::IdentifierExpression>("x");
case 2:
return ast_module_.create<ast::IdentifierExpression>("xy");
case 3:
return ast_module_.create<ast::IdentifierExpression>("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<ast::IdentifierExpression>(namer_.Name(image->result_id())));
params.push_back(
create<ast::IdentifierExpression>(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<ast::IdentifierExpression>(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<ast::type::Texture>();
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<ast::type::Texture>()) {
if (texture_type->Is<ast::type::DepthTexture>()) {
// Convert it to an unsigned integer type.
lod_operand = ast_module_.create<ast::TypeConstructorExpression>(
ast_module_.create<ast::type::U32>(),
ast::ExpressionList{lod_operand});
}
}
if (texture_type->Is<ast::type::DepthTexture>()) {
// Convert it to an unsigned integer type.
lod_operand = ast_module_.create<ast::TypeConstructorExpression>(
ast_module_.create<ast::type::U32>(),
ast::ExpressionList{lod_operand});
}
params.push_back(lod_operand);
image_operands_mask ^= SpvImageOperandsLodMask;
@ -3747,7 +3790,18 @@ bool FunctionEmitter::EmitSampledImageAccess(
auto* ident = create<ast::IdentifierExpression>(builtin_name);
auto* call_expr = create<ast::CallExpression>(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<ast::CallStatement>(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<ast::type::F32>()) {
if (raw_coords.type->is_float_scalar() ||
raw_coords.type->is_integer_scalar()) {
num_coords_supplied = 1;
} else if (raw_coords.type->Is<ast::type::Vector>()) {
num_coords_supplied = raw_coords.type->As<ast::type::Vector>()->size();
} else if (auto* vec_ty = raw_coords.type->As<ast::type::Vector>()) {
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<ast::IdentifierExpression>(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<ast::MemberAccessorExpression>(
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<ast::MemberAccessorExpression>(
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<ast::type::StorageTexture>();
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<ast::type::Vector>()->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<ast::type::Vector>()->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<ast::MemberAccessorExpression>(
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<ast::BitcastExpression>(dest_type, texel_prefix);
}
} // namespace spirv
} // namespace reader
} // namespace tint

View File

@ -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

View File

@ -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<ast::type::U32>();
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<ast::type::I32>();
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<ast::type::F32>();
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<ast::type::Vector>(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<ast::type::Vector>(component_type, 4);
default:
break;
}
Fail() << "unknown format: " << int(format);
return nullptr;
}
bool ParserImpl::RegisterHandleUsage() {
if (!success_) {
return false;

View File

@ -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

View File

@ -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<ImageAccessCase>>;
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<ImageAccessCase>{
// 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<ImageAccessCase>{
// 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<ImageAccessCase>{
// 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<ImageAccessCase>>;
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<ImageAccessCase>{
// 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<ImageAccessCase>{
// 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<ImageAccessCase>{
// 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<ImageAccessCase>
// 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",
{}},
}));