spirv-reader: support OpImageQueryLevels

Bug: tint:109
Change-Id: I1bb7bbdca2322df28e7b22225e4b1bfdb74dbf73
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/39720
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 2a0a872..ddc2c14 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -4365,6 +4365,7 @@
 }
 
 bool FunctionEmitter::EmitImageQuery(const spvtools::opt::Instruction& inst) {
+  // TODO(dneto): Reject cases that are valid in Vulkan but invalid in WGSL.
   const spvtools::opt::Instruction* image = GetImage(inst);
   if (!image) {
     return false;
@@ -4406,7 +4407,22 @@
       return Fail() << "WGSL does not support querying the level of detail of "
                        "an image: "
                     << inst.PrettyPrint();
-    case SpvOpImageQueryLevels:   // TODO(dneto)
+    case SpvOpImageQueryLevels: {
+      auto* levels_ident = create<ast::IdentifierExpression>(
+          Source{}, builder_.Symbols().Register("textureNumLevels"));
+      ast::Expression* ast_expr = create<ast::CallExpression>(
+          Source{}, levels_ident,
+          ast::ExpressionList{GetImageExpression(inst)});
+      auto* result_type = parser_impl_.ConvertType(inst.type_id());
+      // The SPIR-V result type must be integer scalar. The WGSL bulitin
+      // returns i32. If they aren't the same then convert the result.
+      if (result_type != i32_) {
+        ast_expr = create<ast::TypeConstructorExpression>(
+            Source{}, result_type, ast::ExpressionList{ast_expr});
+      }
+      TypedExpression expr{result_type, ast_expr};
+      return EmitConstDefOrWriteToHoistedVar(inst, expr);
+    }
     case SpvOpImageQuerySamples:  // TODO(dneto)
     default:
       break;
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index e8206d5..6c5a7ec 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -4406,6 +4406,293 @@
       }
     })"}}));
 
+INSTANTIATE_TEST_SUITE_P(
+    ImageQueryLevels_SignedResult,
+    SpvParserTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        // In Vulkan:
+        //      Dim must be 1D, 2D, 3D, Cube
+        // WGSL allows 2d, 2d_array, 3d, cube, cube_array
+        // depth_2d, depth_2d_array, depth_cube, depth_cube_array
+
+        // 2D
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // 2D array
+        {"%float 2D 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d_array__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // 3D
+        {"%float 3D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_3d__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // Cube
+        {"%float Cube 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_cube__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // Cube array
+        {"%float Cube 0 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_cube_array__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // depth 2d
+        {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __depth_texture_2d
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // depth 2d array
+        {"%float 2D 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __depth_texture_2d_array
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // depth cube
+        {"%float Cube 1 0 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __depth_texture_cube
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"},
+
+        // depth cube array
+        {"%float Cube 1 1 0 1 Unknown", "%99 = OpImageQueryLevels %int %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __depth_texture_cube_array
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __i32
+        {
+          Call[not set]{
+            Identifier[not set]{textureNumLevels}
+            (
+              Identifier[not set]{x_20}
+            )
+          }
+        }
+      }
+    })"}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    // Spot check that a type conversion is inserted when SPIR-V asks for
+    // an unsigned int result.
+    ImageQueryLevels_UnsignedResult,
+    SpvParserTest_SampledImageAccessTest,
+    ::testing::ValuesIn(std::vector<ImageAccessCase>{
+        {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQueryLevels %uint %im\n",
+         R"(Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+         R"(VariableDeclStatement{
+      VariableConst{
+        x_99
+        none
+        __u32
+        {
+          TypeConstructor[not set]{
+            __u32
+            Call[not set]{
+              Identifier[not set]{textureNumLevels}
+              (
+                Identifier[not set]{x_20}
+              )
+            }
+          }
+        }
+      }
+    })"}}));
+
 struct ImageCoordsCase {
   // SPIR-V image type, excluding result ID and opcode
   std::string spirv_image_type_details;