Browse Source

[spirv] Add support for NonUniformResourceIndex() (#1256)

This intrinsic function is translated using the SPIR-V extension
SPV_EXT_descriptor_indexing.

Also added support for unbounded resource arrays.
They are translated into runtime arrays of resources with the
RuntimeDescriptorArrayEXT capability from the SPIR-V extension
SPV_EXT_descriptor_indexing.
Lei Zhang 7 years ago
parent
commit
a69a83d9de

+ 1 - 0
tools/clang/include/clang/SPIRV/Decoration.h

@@ -126,6 +126,7 @@ public:
   static const Decoration *getInputAttachmentIndex(SPIRVContext &ctx,
   static const Decoration *getInputAttachmentIndex(SPIRVContext &ctx,
                                                    uint32_t index);
                                                    uint32_t index);
   static const Decoration *getAlignment(SPIRVContext &ctx, uint32_t alignment);
   static const Decoration *getAlignment(SPIRVContext &ctx, uint32_t alignment);
+  static const Decoration *getNonUniformEXT(SPIRVContext &ctx);
   static const Decoration *getOverrideCoverageNV(SPIRVContext &ctx);
   static const Decoration *getOverrideCoverageNV(SPIRVContext &ctx);
   static const Decoration *getPassthroughNV(SPIRVContext &ctx);
   static const Decoration *getPassthroughNV(SPIRVContext &ctx);
   static const Decoration *getViewportRelativeNV(SPIRVContext &ctx);
   static const Decoration *getViewportRelativeNV(SPIRVContext &ctx);

+ 1 - 0
tools/clang/include/clang/SPIRV/FeatureManager.h

@@ -34,6 +34,7 @@ enum class Extension {
   KHR_device_group,
   KHR_device_group,
   KHR_multiview,
   KHR_multiview,
   KHR_shader_draw_parameters,
   KHR_shader_draw_parameters,
+  EXT_descriptor_indexing,
   EXT_fragment_fully_covered,
   EXT_fragment_fully_covered,
   EXT_shader_stencil_export,
   EXT_shader_stencil_export,
   AMD_gpu_shader_half_float,
   AMD_gpu_shader_half_float,

+ 12 - 6
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -199,10 +199,13 @@ public:
   /// If residencyCodeId is not zero, the sparse version of the instructions
   /// If residencyCodeId is not zero, the sparse version of the instructions
   /// will be used, and the SPIR-V instruction for storing the resulting
   /// will be used, and the SPIR-V instruction for storing the resulting
   /// residency code will also be emitted.
   /// residency code will also be emitted.
+  ///
+  /// If isNonUniform is true, the sampled image will be decorated with
+  /// NonUniformEXT.
   uint32_t createImageSample(uint32_t texelType, uint32_t imageType,
   uint32_t createImageSample(uint32_t texelType, uint32_t imageType,
                              uint32_t image, uint32_t sampler,
                              uint32_t image, uint32_t sampler,
-                             uint32_t coordinate, uint32_t compareVal,
-                             uint32_t bias, uint32_t lod,
+                             bool isNonUniform, uint32_t coordinate,
+                             uint32_t compareVal, uint32_t bias, uint32_t lod,
                              std::pair<uint32_t, uint32_t> grad,
                              std::pair<uint32_t, uint32_t> grad,
                              uint32_t constOffset, uint32_t varOffset,
                              uint32_t constOffset, uint32_t varOffset,
                              uint32_t constOffsets, uint32_t sample,
                              uint32_t constOffsets, uint32_t sample,
@@ -235,12 +238,15 @@ public:
   /// If residencyCodeId is not zero, the sparse version of the instructions
   /// If residencyCodeId is not zero, the sparse version of the instructions
   /// will be used, and the SPIR-V instruction for storing the resulting
   /// will be used, and the SPIR-V instruction for storing the resulting
   /// residency code will also be emitted.
   /// residency code will also be emitted.
+  /// If isNonUniform is true, the sampled image will be decorated with
+  /// NonUniformEXT.
   uint32_t createImageGather(uint32_t texelType, uint32_t imageType,
   uint32_t createImageGather(uint32_t texelType, uint32_t imageType,
                              uint32_t image, uint32_t sampler,
                              uint32_t image, uint32_t sampler,
-                             uint32_t coordinate, uint32_t component,
-                             uint32_t compareVal, uint32_t constOffset,
-                             uint32_t varOffset, uint32_t constOffsets,
-                             uint32_t sample, uint32_t residencyCodeId);
+                             bool isNonUniform, uint32_t coordinate,
+                             uint32_t component, uint32_t compareVal,
+                             uint32_t constOffset, uint32_t varOffset,
+                             uint32_t constOffsets, uint32_t sample,
+                             uint32_t residencyCodeId);
 
 
   /// \brief Creates an OpImageSparseTexelsResident SPIR-V instruction for the
   /// \brief Creates an OpImageSparseTexelsResident SPIR-V instruction for the
   /// given Resident Code and returns the <result-id> of the instruction.
   /// given Resident Code and returns the <result-id> of the instruction.

+ 27 - 11
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -461,9 +461,8 @@ uint32_t DeclResultIdMapper::getMatrixStructType(const VarDecl *matVar,
 }
 }
 
 
 uint32_t DeclResultIdMapper::createStructOrStructArrayVarOfExplicitLayout(
 uint32_t DeclResultIdMapper::createStructOrStructArrayVarOfExplicitLayout(
-    const DeclContext *decl, uint32_t arraySize,
-    const ContextUsageKind usageKind, llvm::StringRef typeName,
-    llvm::StringRef varName) {
+    const DeclContext *decl, int arraySize, const ContextUsageKind usageKind,
+    llvm::StringRef typeName, llvm::StringRef varName) {
   // cbuffers are translated into OpTypeStruct with Block decoration.
   // cbuffers are translated into OpTypeStruct with Block decoration.
   // tbuffers are translated into OpTypeStruct with BufferBlock decoration.
   // tbuffers are translated into OpTypeStruct with BufferBlock decoration.
   // Push constants are translated into OpTypeStruct with Block decoration.
   // Push constants are translated into OpTypeStruct with Block decoration.
@@ -521,9 +520,16 @@ uint32_t DeclResultIdMapper::createStructOrStructArrayVarOfExplicitLayout(
       theBuilder.getStructType(fieldTypes, typeName, fieldNames, decorations);
       theBuilder.getStructType(fieldTypes, typeName, fieldNames, decorations);
 
 
   // Make an array if requested.
   // Make an array if requested.
-  if (arraySize)
+  if (arraySize > 0) {
     resultType = theBuilder.getArrayType(
     resultType = theBuilder.getArrayType(
         resultType, theBuilder.getConstantUint32(arraySize));
         resultType, theBuilder.getConstantUint32(arraySize));
+  } else if (arraySize == -1) {
+    // Runtime arrays of cbuffer/tbuffer needs additional capability.
+    theBuilder.addExtension(Extension::EXT_descriptor_indexing,
+                            "runtime array of resources", {});
+    theBuilder.requireCapability(spv::Capability::RuntimeDescriptorArrayEXT);
+    resultType = theBuilder.getRuntimeArrayType(resultType);
+  }
 
 
   // Register the <type-id> for this decl
   // Register the <type-id> for this decl
   ctBufferPCTypeIds[decl] = resultType;
   ctBufferPCTypeIds[decl] = resultType;
@@ -569,18 +575,28 @@ uint32_t DeclResultIdMapper::createCTBuffer(const HLSLBufferDecl *decl) {
 }
 }
 
 
 uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
 uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
-  const auto *recordType = decl->getType()->getAs<RecordType>();
-  uint32_t arraySize = 0;
+  const RecordType *recordType = nullptr;
+  int arraySize = 0;
 
 
   // In case we have an array of ConstantBuffer/TextureBuffer:
   // In case we have an array of ConstantBuffer/TextureBuffer:
-  if (!recordType) {
-    if (const auto *arrayType =
+  if (const auto *arrayType = decl->getType()->getAsArrayTypeUnsafe()) {
+    recordType = arrayType->getElementType()->getAs<RecordType>();
+    if (const auto *caType =
             astContext.getAsConstantArrayType(decl->getType())) {
             astContext.getAsConstantArrayType(decl->getType())) {
-      recordType = arrayType->getElementType()->getAs<RecordType>();
-      arraySize = static_cast<uint32_t>(arrayType->getSize().getZExtValue());
+      arraySize = static_cast<uint32_t>(caType->getSize().getZExtValue());
+    } else {
+      arraySize = -1;
     }
     }
+  } else {
+    recordType = decl->getType()->getAs<RecordType>();
   }
   }
-  assert(recordType);
+  if (!recordType) {
+    emitError("constant/texture buffer type %0 unimplemented",
+              decl->getLocStart())
+        << decl->getType();
+    return 0;
+  }
+
   const auto *context = cast<HLSLBufferDecl>(decl->getDeclContext());
   const auto *context = cast<HLSLBufferDecl>(decl->getDeclContext());
   const auto usageKind = context->isCBuffer() ? ContextUsageKind::CBuffer
   const auto usageKind = context->isCBuffer() ? ContextUsageKind::CBuffer
                                               : ContextUsageKind::TBuffer;
                                               : ContextUsageKind::TBuffer;

+ 5 - 1
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -519,9 +519,13 @@ private:
   /// TextureBuffers, and PushConstants. usageKind must be set properly
   /// TextureBuffers, and PushConstants. usageKind must be set properly
   /// depending on the usage kind.
   /// depending on the usage kind.
   ///
   ///
+  /// If arraySize is 0, the variable will be created as a struct ; if arraySize
+  /// is > 0, the variable will be created as an array; if arraySize is -1, the
+  /// variable will be created as a runtime array.
+  ///
   /// Panics if the DeclContext is neither HLSLBufferDecl or RecordDecl.
   /// Panics if the DeclContext is neither HLSLBufferDecl or RecordDecl.
   uint32_t createStructOrStructArrayVarOfExplicitLayout(
   uint32_t createStructOrStructArrayVarOfExplicitLayout(
-      const DeclContext *decl, uint32_t arraySize, ContextUsageKind usageKind,
+      const DeclContext *decl, int arraySize, ContextUsageKind usageKind,
       llvm::StringRef typeName, llvm::StringRef varName);
       llvm::StringRef typeName, llvm::StringRef varName);
 
 
   /// Returns the given decl's HLSL semantic information.
   /// Returns the given decl's HLSL semantic information.

+ 4 - 0
tools/clang/lib/SPIRV/Decoration.cpp

@@ -280,6 +280,10 @@ Decoration::getSecondaryViewportRelativeNV(SPIRVContext &context,
   Decoration d = Decoration(spv::Decoration::SecondaryViewportRelativeNV);
   Decoration d = Decoration(spv::Decoration::SecondaryViewportRelativeNV);
   return getUniqueDecoration(context, d);
   return getUniqueDecoration(context, d);
 }
 }
+const Decoration *Decoration::getNonUniformEXT(SPIRVContext &context) {
+  Decoration d = Decoration(spv::Decoration::NonUniformEXT);
+  return getUniqueDecoration(context, d);
+}
 
 
 const Decoration *Decoration::getHlslCounterBufferGOOGLE(SPIRVContext &context,
 const Decoration *Decoration::getHlslCounterBufferGOOGLE(SPIRVContext &context,
                                                          uint32_t id) {
                                                          uint32_t id) {

+ 4 - 0
tools/clang/lib/SPIRV/FeatureManager.cpp

@@ -100,6 +100,8 @@ Extension FeatureManager::getExtensionSymbol(llvm::StringRef name) {
       .Case("SPV_KHR_multiview", Extension::KHR_multiview)
       .Case("SPV_KHR_multiview", Extension::KHR_multiview)
       .Case("SPV_KHR_shader_draw_parameters",
       .Case("SPV_KHR_shader_draw_parameters",
             Extension::KHR_shader_draw_parameters)
             Extension::KHR_shader_draw_parameters)
+      .Case("SPV_EXT_descriptor_indexing",
+            Extension::EXT_descriptor_indexing)
       .Case("SPV_EXT_fragment_fully_covered",
       .Case("SPV_EXT_fragment_fully_covered",
             Extension::EXT_fragment_fully_covered)
             Extension::EXT_fragment_fully_covered)
       .Case("SPV_EXT_shader_stencil_export",
       .Case("SPV_EXT_shader_stencil_export",
@@ -125,6 +127,8 @@ const char *FeatureManager::getExtensionName(Extension symbol) {
     return "SPV_KHR_multiview";
     return "SPV_KHR_multiview";
   case Extension::KHR_shader_draw_parameters:
   case Extension::KHR_shader_draw_parameters:
     return "SPV_KHR_shader_draw_parameters";
     return "SPV_KHR_shader_draw_parameters";
+  case Extension::EXT_descriptor_indexing:
+    return "SPV_EXT_descriptor_indexing";
   case Extension::EXT_fragment_fully_covered:
   case Extension::EXT_fragment_fully_covered:
     return "SPV_EXT_fragment_fully_covered";
     return "SPV_EXT_fragment_fully_covered";
   case Extension::EXT_shader_stencil_export:
   case Extension::EXT_shader_stencil_export:

+ 20 - 5
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -442,8 +442,8 @@ uint32_t ModuleBuilder::createImageTexelPointer(uint32_t resultType,
 
 
 uint32_t ModuleBuilder::createImageSample(
 uint32_t ModuleBuilder::createImageSample(
     uint32_t texelType, uint32_t imageType, uint32_t image, uint32_t sampler,
     uint32_t texelType, uint32_t imageType, uint32_t image, uint32_t sampler,
-    uint32_t coordinate, uint32_t compareVal, uint32_t bias, uint32_t lod,
-    std::pair<uint32_t, uint32_t> grad, uint32_t constOffset,
+    bool isNonUniform, uint32_t coordinate, uint32_t compareVal, uint32_t bias,
+    uint32_t lod, std::pair<uint32_t, uint32_t> grad, uint32_t constOffset,
     uint32_t varOffset, uint32_t constOffsets, uint32_t sample, uint32_t minLod,
     uint32_t varOffset, uint32_t constOffsets, uint32_t sample, uint32_t minLod,
     uint32_t residencyCodeId) {
     uint32_t residencyCodeId) {
   assert(insertPoint && "null insert point");
   assert(insertPoint && "null insert point");
@@ -470,6 +470,12 @@ uint32_t ModuleBuilder::createImageSample(
   instBuilder.opSampledImage(sampledImgTy, sampledImgId, image, sampler).x();
   instBuilder.opSampledImage(sampledImgTy, sampledImgId, image, sampler).x();
   insertPoint->appendInstruction(std::move(constructSite));
   insertPoint->appendInstruction(std::move(constructSite));
 
 
+  if (isNonUniform) {
+    // The sampled image will be used to access resource's memory, so we need
+    // to decorate it with NonUniformEXT.
+    decorate(sampledImgId, spv::Decoration::NonUniformEXT);
+  }
+
   uint32_t texelId = theContext.takeNextId();
   uint32_t texelId = theContext.takeNextId();
   llvm::SmallVector<uint32_t, 4> params;
   llvm::SmallVector<uint32_t, 4> params;
   const auto mask =
   const auto mask =
@@ -550,9 +556,9 @@ uint32_t ModuleBuilder::createImageFetchOrRead(
 
 
 uint32_t ModuleBuilder::createImageGather(
 uint32_t ModuleBuilder::createImageGather(
     uint32_t texelType, uint32_t imageType, uint32_t image, uint32_t sampler,
     uint32_t texelType, uint32_t imageType, uint32_t image, uint32_t sampler,
-    uint32_t coordinate, uint32_t component, uint32_t compareVal,
-    uint32_t constOffset, uint32_t varOffset, uint32_t constOffsets,
-    uint32_t sample, uint32_t residencyCodeId) {
+    bool isNonUniform, uint32_t coordinate, uint32_t component,
+    uint32_t compareVal, uint32_t constOffset, uint32_t varOffset,
+    uint32_t constOffsets, uint32_t sample, uint32_t residencyCodeId) {
   assert(insertPoint && "null insert point");
   assert(insertPoint && "null insert point");
 
 
   uint32_t sparseRetType = 0;
   uint32_t sparseRetType = 0;
@@ -567,6 +573,12 @@ uint32_t ModuleBuilder::createImageGather(
   instBuilder.opSampledImage(sampledImgTy, sampledImgId, image, sampler).x();
   instBuilder.opSampledImage(sampledImgTy, sampledImgId, image, sampler).x();
   insertPoint->appendInstruction(std::move(constructSite));
   insertPoint->appendInstruction(std::move(constructSite));
 
 
+  if (isNonUniform) {
+    // The sampled image will be used to access resource's memory, so we need
+    // to decorate it with NonUniformEXT.
+    decorate(sampledImgId, spv::Decoration::NonUniformEXT);
+  }
+
   llvm::SmallVector<uint32_t, 2> params;
   llvm::SmallVector<uint32_t, 2> params;
 
 
   // TODO: Update ImageGather to accept minLod if necessary.
   // TODO: Update ImageGather to accept minLod if necessary.
@@ -893,6 +905,9 @@ void ModuleBuilder::decorate(uint32_t targetId, spv::Decoration decoration) {
   case spv::Decoration::Patch:
   case spv::Decoration::Patch:
     d = Decoration::getPatch(theContext);
     d = Decoration::getPatch(theContext);
     break;
     break;
+  case spv::Decoration::NonUniformEXT:
+    d = Decoration::getNonUniformEXT(theContext);
+    break;
   }
   }
 
 
   assert(d && "unimplemented decoration");
   assert(d && "unimplemented decoration");

+ 169 - 57
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -533,6 +533,37 @@ std::string getFnName(const FunctionDecl *fn) {
   return getNamespacePrefix(fn) + classOrStructName + fn->getName().str();
   return getNamespacePrefix(fn) + classOrStructName + fn->getName().str();
 }
 }
 
 
+/// Returns the capability required to non-uniformly index into the given type.
+spv::Capability getNonUniformCapability(QualType type) {
+  using spv::Capability;
+
+  if (type->isArrayType()) {
+    return getNonUniformCapability(
+        type->getAsArrayTypeUnsafe()->getElementType());
+  }
+  if (TypeTranslator::isTexture(type) || TypeTranslator::isSampler(type)) {
+    return Capability::SampledImageArrayNonUniformIndexingEXT;
+  }
+  if (TypeTranslator::isRWTexture(type)) {
+    return Capability::StorageImageArrayNonUniformIndexingEXT;
+  }
+  if (TypeTranslator::isBuffer(type)) {
+    return Capability::UniformTexelBufferArrayNonUniformIndexingEXT;
+  }
+  if (TypeTranslator::isRWBuffer(type)) {
+    return Capability::StorageTexelBufferArrayNonUniformIndexingEXT;
+  }
+  if (const auto *recordType = type->getAs<RecordType>()) {
+    const auto name = recordType->getDecl()->getName();
+
+    if (name == "SubpassInput" || name == "SubpassInputMS") {
+      return Capability::InputAttachmentArrayNonUniformIndexingEXT;
+    }
+  }
+
+  return Capability::Max;
+}
+
 } // namespace
 } // namespace
 
 
 SPIRVEmitter::SPIRVEmitter(CompilerInstance &ci, EmitSPIRVOptions &options)
 SPIRVEmitter::SPIRVEmitter(CompilerInstance &ci, EmitSPIRVOptions &options)
@@ -548,7 +579,7 @@ SPIRVEmitter::SPIRVEmitter(CompilerInstance &ci, EmitSPIRVOptions &options)
                    featureManager, options),
                    featureManager, options),
       entryFunctionId(0), curFunction(nullptr), curThis(0),
       entryFunctionId(0), curFunction(nullptr), curThis(0),
       seenPushConstantAt(), isSpecConstantMode(false),
       seenPushConstantAt(), isSpecConstantMode(false),
-      needsLegalization(false) {
+      foundNonUniformResourceIndex(false), needsLegalization(false) {
   if (shaderModel.GetKind() == hlsl::ShaderModel::Kind::Invalid)
   if (shaderModel.GetKind() == hlsl::ShaderModel::Kind::Invalid)
     emitError("unknown shader module: %0", {}) << shaderModel.GetName();
     emitError("unknown shader module: %0", {}) << shaderModel.GetName();
 
 
@@ -860,6 +891,12 @@ SpirvEvalInfo SPIRVEmitter::loadIfGLValue(const Expr *expr,
 
 
   uint32_t loadedId = theBuilder.createLoad(valType, info);
   uint32_t loadedId = theBuilder.createLoad(valType, info);
 
 
+  // Decorate with NonUniformEXT if loading from a pointer with that property.
+  // We are likely loading an element from the resource array here.
+  if (info.isNonUniform()) {
+    theBuilder.decorate(loadedId, spv::Decoration::NonUniformEXT);
+  }
+
   // Special-case: According to the SPIR-V Spec: There is no physical size or
   // Special-case: According to the SPIR-V Spec: There is no physical size or
   // bit pattern defined for boolean type. Therefore an unsigned integer is used
   // bit pattern defined for boolean type. Therefore an unsigned integer is used
   // to represent booleans when layout is required. In such cases, after loading
   // to represent booleans when layout is required. In such cases, after loading
@@ -1871,8 +1908,20 @@ void SPIRVEmitter::doSwitchStmt(const SwitchStmt *switchStmt,
 
 
 SpirvEvalInfo
 SpirvEvalInfo
 SPIRVEmitter::doArraySubscriptExpr(const ArraySubscriptExpr *expr) {
 SPIRVEmitter::doArraySubscriptExpr(const ArraySubscriptExpr *expr) {
+  // Make sure we don't have previously unhandled NonUniformResourceIndex()
+  assert(!foundNonUniformResourceIndex);
+
   llvm::SmallVector<uint32_t, 4> indices;
   llvm::SmallVector<uint32_t, 4> indices;
-  auto info = loadIfAliasVarRef(collectArrayStructIndices(expr, &indices));
+  const auto *base = collectArrayStructIndices(expr, &indices);
+  auto info = loadIfAliasVarRef(base);
+
+  if (foundNonUniformResourceIndex) {
+    // Add the necessary capability required for indexing into this kind
+    // of resource
+    theBuilder.requireCapability(getNonUniformCapability(base->getType()));
+    info.setNonUniform(); // Carry forward the NonUniformEXT decoration
+    foundNonUniformResourceIndex = false;
+  }
 
 
   if (!indices.empty()) {
   if (!indices.empty()) {
     (void)turnIntoElementPtr(info, expr->getType(), indices);
     (void)turnIntoElementPtr(info, expr->getType(), indices);
@@ -2853,13 +2902,19 @@ SPIRVEmitter::processTextureLevelOfDetail(const CXXMemberCallExpr *expr) {
   // Return type is always a single float (LOD).
   // Return type is always a single float (LOD).
   assert(expr->getNumArgs() == 2u);
   assert(expr->getNumArgs() == 2u);
   const auto *object = expr->getImplicitObjectArgument();
   const auto *object = expr->getImplicitObjectArgument();
-  const uint32_t objectId = loadIfGLValue(object);
-  const uint32_t samplerState = doExpr(expr->getArg(0));
+  const auto objectInfo = loadIfGLValue(object);
+  const auto samplerState = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t sampledImageType = theBuilder.getSampledImageType(
   const uint32_t sampledImageType = theBuilder.getSampledImageType(
       typeTranslator.translateType(object->getType()));
       typeTranslator.translateType(object->getType()));
   const uint32_t sampledImage = theBuilder.createBinaryOp(
   const uint32_t sampledImage = theBuilder.createBinaryOp(
-      spv::Op::OpSampledImage, sampledImageType, objectId, samplerState);
+      spv::Op::OpSampledImage, sampledImageType, objectInfo, samplerState);
+
+  if (objectInfo.isNonUniform() || samplerState.isNonUniform()) {
+    // The sampled image will be used to access resource's memory, so we need
+    // to decorate it with NonUniformEXT.
+    theBuilder.decorate(sampledImage, spv::Decoration::NonUniformEXT);
+  }
 
 
   // The result type of OpImageQueryLod must be a float2.
   // The result type of OpImageQueryLod must be a float2.
   const uint32_t queryResultType =
   const uint32_t queryResultType =
@@ -2913,8 +2968,8 @@ uint32_t SPIRVEmitter::processTextureGatherRGBACmpRGBA(
   // No offset args for TextureCube, 1 or 4 offset args for the rest.
   // No offset args for TextureCube, 1 or 4 offset args for the rest.
   assert(numOffsetArgs == 0 || numOffsetArgs == 1 || numOffsetArgs == 4);
   assert(numOffsetArgs == 0 || numOffsetArgs == 1 || numOffsetArgs == 4);
 
 
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t compareVal = isCmp ? doExpr(expr->getArg(2)) : 0;
   const uint32_t compareVal = isCmp ? doExpr(expr->getArg(2)) : 0;
 
 
@@ -2946,6 +3001,7 @@ uint32_t SPIRVEmitter::processTextureGatherRGBACmpRGBA(
   }
   }
 
 
   const auto status = hasStatusArg ? doExpr(expr->getArg(numArgs - 1)) : 0;
   const auto status = hasStatusArg ? doExpr(expr->getArg(numArgs - 1)) : 0;
+  const bool isNonUniform = image.isNonUniform() || sampler.isNonUniform();
 
 
   if (needsEmulation) {
   if (needsEmulation) {
     const auto elemType = typeTranslator.translateType(
     const auto elemType = typeTranslator.translateType(
@@ -2955,7 +3011,7 @@ uint32_t SPIRVEmitter::processTextureGatherRGBACmpRGBA(
     for (uint32_t i = 0; i < 4; ++i) {
     for (uint32_t i = 0; i < 4; ++i) {
       varOffset = doExpr(expr->getArg(2 + isCmp + i));
       varOffset = doExpr(expr->getArg(2 + isCmp + i));
       const uint32_t gatherRet = theBuilder.createImageGather(
       const uint32_t gatherRet = theBuilder.createImageGather(
-          retTypeId, imageTypeId, image, sampler, coordinate,
+          retTypeId, imageTypeId, image, sampler, isNonUniform, coordinate,
           theBuilder.getConstantInt32(component), compareVal, /*constOffset*/ 0,
           theBuilder.getConstantInt32(component), compareVal, /*constOffset*/ 0,
           varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0, status);
           varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0, status);
       texels[i] = theBuilder.createCompositeExtract(elemType, gatherRet, {i});
       texels[i] = theBuilder.createCompositeExtract(elemType, gatherRet, {i});
@@ -2965,7 +3021,7 @@ uint32_t SPIRVEmitter::processTextureGatherRGBACmpRGBA(
   }
   }
 
 
   return theBuilder.createImageGather(
   return theBuilder.createImageGather(
-      retTypeId, imageTypeId, image, sampler, coordinate,
+      retTypeId, imageTypeId, image, sampler, isNonUniform, coordinate,
       theBuilder.getConstantInt32(component), compareVal, constOffset,
       theBuilder.getConstantInt32(component), compareVal, constOffset,
       varOffset, constOffsets, /*sampleNumber*/ 0, status);
       varOffset, constOffsets, /*sampleNumber*/ 0, status);
 }
 }
@@ -2999,8 +3055,8 @@ uint32_t SPIRVEmitter::processTextureGatherCmp(const CXXMemberCallExpr *expr) {
   const bool hasOffsetArg = (numArgs == 5) || (numArgs == 4 && !hasStatusArg);
   const bool hasOffsetArg = (numArgs == 5) || (numArgs == 4 && !hasStatusArg);
 
 
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const auto *imageExpr = expr->getImplicitObjectArgument();
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t comparator = doExpr(expr->getArg(2));
   const uint32_t comparator = doExpr(expr->getArg(2));
   uint32_t constOffset = 0, varOffset = 0;
   uint32_t constOffset = 0, varOffset = 0;
@@ -3012,8 +3068,9 @@ uint32_t SPIRVEmitter::processTextureGatherCmp(const CXXMemberCallExpr *expr) {
   const auto status = hasStatusArg ? doExpr(expr->getArg(numArgs - 1)) : 0;
   const auto status = hasStatusArg ? doExpr(expr->getArg(numArgs - 1)) : 0;
 
 
   return theBuilder.createImageGather(
   return theBuilder.createImageGather(
-      retType, imageType, image, sampler, coordinate, /*component*/ 0,
-      comparator, constOffset, varOffset, /*constOffsets*/ 0,
+      retType, imageType, image, sampler,
+      image.isNonUniform() || sampler.isNonUniform(), coordinate,
+      /*component*/ 0, comparator, constOffset, varOffset, /*constOffsets*/ 0,
       /*sampleNumber*/ 0, status);
       /*sampleNumber*/ 0, status);
 }
 }
 
 
@@ -3031,7 +3088,12 @@ SpirvEvalInfo SPIRVEmitter::processBufferTextureLoad(
   const bool doFetch =
   const bool doFetch =
       TypeTranslator::isBuffer(type) || TypeTranslator::isTexture(type);
       TypeTranslator::isBuffer(type) || TypeTranslator::isTexture(type);
 
 
-  const uint32_t objectId = loadIfGLValue(object);
+  const auto objectInfo = loadIfGLValue(object);
+
+  if (objectInfo.isNonUniform()) {
+    // Decoreate the image handle for OpImageFetch/OpImageRead
+    theBuilder.decorate(objectInfo, spv::Decoration::NonUniformEXT);
+  }
 
 
   // For Texture2DMS and Texture2DMSArray, Sample must be used rather than Lod.
   // For Texture2DMS and Texture2DMSArray, Sample must be used rather than Lod.
   uint32_t sampleNumber = 0;
   uint32_t sampleNumber = 0;
@@ -3060,7 +3122,7 @@ SpirvEvalInfo SPIRVEmitter::processBufferTextureLoad(
   // OpImageFetch and OpImageRead can only fetch a vector of 4 elements.
   // OpImageFetch and OpImageRead can only fetch a vector of 4 elements.
   const uint32_t texelTypeId = theBuilder.getVecType(elemTypeId, 4u);
   const uint32_t texelTypeId = theBuilder.getVecType(elemTypeId, 4u);
   const uint32_t texel = theBuilder.createImageFetchOrRead(
   const uint32_t texel = theBuilder.createImageFetchOrRead(
-      doFetch, texelTypeId, type, objectId, locationId, lod, constOffset,
+      doFetch, texelTypeId, type, objectInfo, locationId, lod, constOffset,
       varOffset, /*constOffsets*/ 0, sampleNumber, residencyCode);
       varOffset, /*constOffsets*/ 0, sampleNumber, residencyCode);
 
 
   // If the result type is a vec1, vec2, or vec3, some extra processing
   // If the result type is a vec1, vec2, or vec3, some extra processing
@@ -3775,8 +3837,8 @@ SPIRVEmitter::processIntrinsicMemberCall(const CXXMemberCallExpr *expr,
 
 
 uint32_t SPIRVEmitter::createImageSample(
 uint32_t SPIRVEmitter::createImageSample(
     QualType retType, uint32_t imageType, uint32_t image, uint32_t sampler,
     QualType retType, uint32_t imageType, uint32_t image, uint32_t sampler,
-    uint32_t coordinate, uint32_t compareVal, uint32_t bias, uint32_t lod,
-    std::pair<uint32_t, uint32_t> grad, uint32_t constOffset,
+    bool isNonUniform, uint32_t coordinate, uint32_t compareVal, uint32_t bias,
+    uint32_t lod, std::pair<uint32_t, uint32_t> grad, uint32_t constOffset,
     uint32_t varOffset, uint32_t constOffsets, uint32_t sample, uint32_t minLod,
     uint32_t varOffset, uint32_t constOffsets, uint32_t sample, uint32_t minLod,
     uint32_t residencyCodeId) {
     uint32_t residencyCodeId) {
 
 
@@ -3785,10 +3847,10 @@ uint32_t SPIRVEmitter::createImageSample(
   // SampleDref* instructions in SPIR-V always return a scalar.
   // SampleDref* instructions in SPIR-V always return a scalar.
   // They also have the correct type in HLSL.
   // They also have the correct type in HLSL.
   if (compareVal) {
   if (compareVal) {
-    return theBuilder.createImageSample(retTypeId, imageType, image, sampler,
-                                        coordinate, compareVal, bias, lod, grad,
-                                        constOffset, varOffset, constOffsets,
-                                        sample, minLod, residencyCodeId);
+    return theBuilder.createImageSample(
+        retTypeId, imageType, image, sampler, isNonUniform, coordinate,
+        compareVal, bias, lod, grad, constOffset, varOffset, constOffsets,
+        sample, minLod, residencyCodeId);
   }
   }
 
 
   // Non-Dref Sample instructions in SPIR-V must always return a vec4.
   // Non-Dref Sample instructions in SPIR-V must always return a vec4.
@@ -3815,9 +3877,9 @@ uint32_t SPIRVEmitter::createImageSample(
     needsLegalization = true;
     needsLegalization = true;
 
 
   uint32_t retVal = theBuilder.createImageSample(
   uint32_t retVal = theBuilder.createImageSample(
-      texelTypeId, imageType, image, sampler, coordinate, compareVal, bias, lod,
-      grad, constOffset, varOffset, constOffsets, sample, minLod,
-      residencyCodeId);
+      texelTypeId, imageType, image, sampler, isNonUniform, coordinate,
+      compareVal, bias, lod, grad, constOffset, varOffset, constOffsets, sample,
+      minLod, residencyCodeId);
 
 
   // Extract smaller vector from the vec4 result if necessary.
   // Extract smaller vector from the vec4 result if necessary.
   if (texelTypeId != retTypeId) {
   if (texelTypeId != retTypeId) {
@@ -3874,24 +3936,26 @@ uint32_t SPIRVEmitter::processTextureSampleGather(const CXXMemberCallExpr *expr,
 
 
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   // .Sample()/.Gather() may have a third optional paramter for offset.
   // .Sample()/.Gather() may have a third optional paramter for offset.
   uint32_t constOffset = 0, varOffset = 0;
   uint32_t constOffset = 0, varOffset = 0;
   if (hasOffsetArg)
   if (hasOffsetArg)
     handleOffsetInMethodCall(expr, 2, &constOffset, &varOffset);
     handleOffsetInMethodCall(expr, 2, &constOffset, &varOffset);
+  const bool isNonUniform = image.isNonUniform() || sampler.isNonUniform();
 
 
   const auto retType = expr->getDirectCallee()->getReturnType();
   const auto retType = expr->getDirectCallee()->getReturnType();
   const auto retTypeId = typeTranslator.translateType(retType);
   const auto retTypeId = typeTranslator.translateType(retType);
   if (isSample) {
   if (isSample) {
     return createImageSample(
     return createImageSample(
-        retType, imageType, image, sampler, coordinate, /*compareVal*/ 0,
-        /*bias*/ 0, /*lod*/ 0, std::make_pair(0, 0), constOffset, varOffset,
-        /*constOffsets*/ 0, /*sampleNumber*/ 0, /*minLod*/ clamp, status);
+        retType, imageType, image, sampler, isNonUniform, coordinate,
+        /*compareVal*/ 0, /*bias*/ 0, /*lod*/ 0, std::make_pair(0, 0),
+        constOffset, varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0,
+        /*minLod*/ clamp, status);
   } else {
   } else {
     return theBuilder.createImageGather(
     return theBuilder.createImageGather(
-        retTypeId, imageType, image, sampler, coordinate,
+        retTypeId, imageType, image, sampler, isNonUniform, coordinate,
         // .Gather() doc says we return four components of red data.
         // .Gather() doc says we return four components of red data.
         theBuilder.getConstantInt32(0), /*compareVal*/ 0, constOffset,
         theBuilder.getConstantInt32(0), /*compareVal*/ 0, constOffset,
         varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0, status);
         varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0, status);
@@ -3951,8 +4015,8 @@ SPIRVEmitter::processTextureSampleBiasLevel(const CXXMemberCallExpr *expr,
 
 
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   uint32_t lod = 0;
   uint32_t lod = 0;
   uint32_t bias = 0;
   uint32_t bias = 0;
@@ -3968,10 +4032,11 @@ SPIRVEmitter::processTextureSampleBiasLevel(const CXXMemberCallExpr *expr,
 
 
   const auto retType = expr->getDirectCallee()->getReturnType();
   const auto retType = expr->getDirectCallee()->getReturnType();
 
 
-  return createImageSample(retType, imageType, image, sampler, coordinate,
-                           /*compareVal*/ 0, bias, lod, std::make_pair(0, 0),
-                           constOffset, varOffset, /*constOffsets*/ 0,
-                           /*sampleNumber*/ 0, /*minLod*/ clamp, status);
+  return createImageSample(
+      retType, imageType, image, sampler,
+      image.isNonUniform() || sampler.isNonUniform(), coordinate,
+      /*compareVal*/ 0, bias, lod, std::make_pair(0, 0), constOffset, varOffset,
+      /*constOffsets*/ 0, /*sampleNumber*/ 0, /*minLod*/ clamp, status);
 }
 }
 
 
 uint32_t SPIRVEmitter::processTextureSampleGrad(const CXXMemberCallExpr *expr) {
 uint32_t SPIRVEmitter::processTextureSampleGrad(const CXXMemberCallExpr *expr) {
@@ -4011,8 +4076,8 @@ uint32_t SPIRVEmitter::processTextureSampleGrad(const CXXMemberCallExpr *expr) {
 
 
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
   const uint32_t imageType = typeTranslator.translateType(imageExpr->getType());
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t ddx = doExpr(expr->getArg(2));
   const uint32_t ddx = doExpr(expr->getArg(2));
   const uint32_t ddy = doExpr(expr->getArg(3));
   const uint32_t ddy = doExpr(expr->getArg(3));
@@ -4023,9 +4088,11 @@ uint32_t SPIRVEmitter::processTextureSampleGrad(const CXXMemberCallExpr *expr) {
 
 
   const auto retType = expr->getDirectCallee()->getReturnType();
   const auto retType = expr->getDirectCallee()->getReturnType();
   return createImageSample(
   return createImageSample(
-      retType, imageType, image, sampler, coordinate, /*compareVal*/ 0,
-      /*bias*/ 0, /*lod*/ 0, std::make_pair(ddx, ddy), constOffset, varOffset,
-      /*constOffsets*/ 0, /*sampleNumber*/ 0, /*minLod*/ clamp, status);
+      retType, imageType, image, sampler,
+      image.isNonUniform() || sampler.isNonUniform(), coordinate,
+      /*compareVal*/ 0, /*bias*/ 0, /*lod*/ 0, std::make_pair(ddx, ddy),
+      constOffset, varOffset, /*constOffsets*/ 0, /*sampleNumber*/ 0,
+      /*minLod*/ clamp, status);
 }
 }
 
 
 uint32_t
 uint32_t
@@ -4095,8 +4162,8 @@ SPIRVEmitter::processTextureSampleCmpCmpLevelZero(const CXXMemberCallExpr *expr,
   const bool hasOffsetArg = numArgs - hasClampArg - hasStatusArg - 3 > 0;
   const bool hasOffsetArg = numArgs - hasClampArg - hasStatusArg - 3 > 0;
 
 
   const auto *imageExpr = expr->getImplicitObjectArgument();
   const auto *imageExpr = expr->getImplicitObjectArgument();
-  const uint32_t image = loadIfGLValue(imageExpr);
-  const uint32_t sampler = doExpr(expr->getArg(0));
+  const auto image = loadIfGLValue(imageExpr);
+  const auto sampler = doExpr(expr->getArg(0));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t coordinate = doExpr(expr->getArg(1));
   const uint32_t compareVal = doExpr(expr->getArg(2));
   const uint32_t compareVal = doExpr(expr->getArg(2));
   // If offset is present in .SampleCmp(), it will be the fourth argument.
   // If offset is present in .SampleCmp(), it will be the fourth argument.
@@ -4117,10 +4184,11 @@ SPIRVEmitter::processTextureSampleCmpCmpLevelZero(const CXXMemberCallExpr *expr,
   const auto imageType = typeTranslator.translateResourceType(
   const auto imageType = typeTranslator.translateResourceType(
       imageExpr->getType(), LayoutRule::Void, /*isDepthCmp=*/true);
       imageExpr->getType(), LayoutRule::Void, /*isDepthCmp=*/true);
 
 
-  return createImageSample(retType, imageType, image, sampler, coordinate,
-                           compareVal, /*bias*/ 0, lod, std::make_pair(0, 0),
-                           constOffset, varOffset, /*constOffsets*/ 0,
-                           /*sampleNumber*/ 0, /*minLod*/ clamp, status);
+  return createImageSample(
+      retType, imageType, image, sampler,
+      image.isNonUniform() || sampler.isNonUniform(), coordinate, compareVal,
+      /*bias*/ 0, lod, std::make_pair(0, 0), constOffset, varOffset,
+      /*constOffsets*/ 0, /*sampleNumber*/ 0, /*minLod*/ clamp, status);
 }
 }
 
 
 SpirvEvalInfo
 SpirvEvalInfo
@@ -4532,19 +4600,21 @@ SpirvEvalInfo SPIRVEmitter::doUnaryOperator(const UnaryOperator *expr) {
 
 
     // Prefix increment/decrement operator returns a lvalue, while postfix
     // Prefix increment/decrement operator returns a lvalue, while postfix
     // increment/decrement returns a rvalue.
     // increment/decrement returns a rvalue.
-    return isPre ? subValue : SpirvEvalInfo(originValue).setRValue();
+    return isPre ? subValue : subValue.setResultId(originValue).setRValue();
   }
   }
   case UO_Not: {
   case UO_Not: {
-    const auto valId =
-        theBuilder.createUnaryOp(spv::Op::OpNot, subTypeId, subValue);
-    return SpirvEvalInfo(valId).setRValue();
+    return subValue
+        .setResultId(
+            theBuilder.createUnaryOp(spv::Op::OpNot, subTypeId, subValue))
+        .setRValue();
   }
   }
   case UO_LNot: {
   case UO_LNot: {
     // Parsing will do the necessary casting to make sure we are applying the
     // Parsing will do the necessary casting to make sure we are applying the
     // ! operator on boolean values.
     // ! operator on boolean values.
-    const auto valId =
-        theBuilder.createUnaryOp(spv::Op::OpLogicalNot, subTypeId, subValue);
-    return SpirvEvalInfo(valId).setRValue();
+    return subValue
+        .setResultId(theBuilder.createUnaryOp(spv::Op::OpLogicalNot, subTypeId,
+                                              subValue))
+        .setRValue();
   }
   }
   case UO_Plus:
   case UO_Plus:
     // No need to do anything for the prefix + operator.
     // No need to do anything for the prefix + operator.
@@ -4553,8 +4623,9 @@ SpirvEvalInfo SPIRVEmitter::doUnaryOperator(const UnaryOperator *expr) {
     // SPIR-V have two opcodes for negating values: OpSNegate and OpFNegate.
     // SPIR-V have two opcodes for negating values: OpSNegate and OpFNegate.
     const spv::Op spvOp = isFloatOrVecOfFloatType(subType) ? spv::Op::OpFNegate
     const spv::Op spvOp = isFloatOrVecOfFloatType(subType) ? spv::Op::OpFNegate
                                                            : spv::Op::OpSNegate;
                                                            : spv::Op::OpSNegate;
-    const auto valId = theBuilder.createUnaryOp(spvOp, subTypeId, subValue);
-    return SpirvEvalInfo(valId).setRValue();
+    return subValue
+        .setResultId(theBuilder.createUnaryOp(spvOp, subTypeId, subValue))
+        .setRValue();
   }
   }
   default:
   default:
     break;
     break;
@@ -5062,8 +5133,14 @@ SpirvEvalInfo SPIRVEmitter::processBinaryOp(const Expr *lhs, const Expr *rhs,
     }
     }
 
 
     auto result = SpirvEvalInfo(valId).setRValue();
     auto result = SpirvEvalInfo(valId).setRValue();
+
+    // Propagate RelaxedPrecision
     if (lhsVal.isRelaxedPrecision() || rhsVal.isRelaxedPrecision())
     if (lhsVal.isRelaxedPrecision() || rhsVal.isRelaxedPrecision())
       result.setRelaxedPrecision();
       result.setRelaxedPrecision();
+    // Propagate NonUniformEXT
+    if (lhsVal.isNonUniform() || rhsVal.isNonUniform())
+      result.setNonUniform();
+
     return result;
     return result;
   }
   }
   case BO_Assign:
   case BO_Assign:
@@ -5559,9 +5636,14 @@ SPIRVEmitter::tryToAssignToRWBufferRWTexture(const Expr *lhs,
   if (isBufferTextureIndexing(lhsExpr, &baseExpr, &indexExpr)) {
   if (isBufferTextureIndexing(lhsExpr, &baseExpr, &indexExpr)) {
     const uint32_t locId = doExpr(indexExpr);
     const uint32_t locId = doExpr(indexExpr);
     const QualType imageType = baseExpr->getType();
     const QualType imageType = baseExpr->getType();
+    const auto baseInfo = doExpr(baseExpr);
     const uint32_t imageId = theBuilder.createLoad(
     const uint32_t imageId = theBuilder.createLoad(
-        typeTranslator.translateType(imageType), doExpr(baseExpr));
+        typeTranslator.translateType(imageType), baseInfo);
     theBuilder.createImageWrite(imageType, imageId, locId, rhs);
     theBuilder.createImageWrite(imageType, imageId, locId, rhs);
+    if (baseInfo.isNonUniform()) {
+      // Decorate the image handle for OpImageWrite
+      theBuilder.decorate(imageId, spv::Decoration::NonUniformEXT);
+    }
     return rhs;
     return rhs;
   }
   }
   return 0;
   return 0;
@@ -6172,6 +6254,9 @@ SpirvEvalInfo SPIRVEmitter::processIntrinsicCallExpr(const CallExpr *callExpr) {
   case hlsl::IntrinsicOp::IOP_InterlockedCompareExchange:
   case hlsl::IntrinsicOp::IOP_InterlockedCompareExchange:
     retVal = processIntrinsicInterlockedMethod(callExpr, hlslOpcode);
     retVal = processIntrinsicInterlockedMethod(callExpr, hlslOpcode);
     break;
     break;
+  case hlsl::IntrinsicOp::IOP_NonUniformResourceIndex:
+    retVal = processIntrinsicNonUniformResourceIndex(callExpr);
+    break;
   case hlsl::IntrinsicOp::IOP_tex1D:
   case hlsl::IntrinsicOp::IOP_tex1D:
   case hlsl::IntrinsicOp::IOP_tex1Dbias:
   case hlsl::IntrinsicOp::IOP_tex1Dbias:
   case hlsl::IntrinsicOp::IOP_tex1Dgrad:
   case hlsl::IntrinsicOp::IOP_tex1Dgrad:
@@ -6558,6 +6643,11 @@ SPIRVEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr,
       }
       }
       const auto coordId = doExpr(index);
       const auto coordId = doExpr(index);
       ptr = theBuilder.createImageTexelPointer(ptrType, baseId, coordId, zero);
       ptr = theBuilder.createImageTexelPointer(ptrType, baseId, coordId, zero);
+      if (baseId.isNonUniform()) {
+        // Image texel pointer will used to access image memory. Vulkan requires
+        // it to be decorated with NonUniformEXT.
+        theBuilder.decorate(ptr, spv::Decoration::NonUniformEXT);
+      }
     }
     }
   }
   }
   if (!ptr)
   if (!ptr)
@@ -6601,6 +6691,28 @@ SPIRVEmitter::processIntrinsicInterlockedMethod(const CallExpr *expr,
   return 0;
   return 0;
 }
 }
 
 
+SpirvEvalInfo
+SPIRVEmitter::processIntrinsicNonUniformResourceIndex(const CallExpr *expr) {
+  foundNonUniformResourceIndex = true;
+  theBuilder.addExtension(Extension::EXT_descriptor_indexing,
+                          "NonUniformResourceIndex", expr->getExprLoc());
+  theBuilder.requireCapability(spv::Capability::ShaderNonUniformEXT);
+
+  auto index = doExpr(expr->getArg(0)).setNonUniform();
+  // Decorate the expression in NonUniformResourceIndex() with NonUniformEXT.
+  // Aside from this, we also need to eventually populate the NonUniformEXT
+  // status to the usage of this expression: the "pointer" operand to a memory
+  // access instruction. Vulkan spec has the following rules:
+  //
+  // If an instruction loads from or stores to a resource (including atomics and
+  // image instructions) and the resource descriptor being accessed is not
+  // dynamically uniform, then the operand corresponding to that resource (e.g.
+  // the pointer or sampled image operand) must be decorated with NonUniformEXT.
+  theBuilder.decorate(index, spv::Decoration::NonUniformEXT);
+
+  return index;
+}
+
 uint32_t SPIRVEmitter::processIntrinsicMsad4(const CallExpr *callExpr) {
 uint32_t SPIRVEmitter::processIntrinsicMsad4(const CallExpr *callExpr) {
   emitWarning("msad4 intrinsic function is emulated using many SPIR-V "
   emitWarning("msad4 intrinsic function is emulated using many SPIR-V "
               "instructions due to lack of direct SPIR-V equivalent",
               "instructions due to lack of direct SPIR-V equivalent",

+ 12 - 2
tools/clang/lib/SPIRV/SPIRVEmitter.h

@@ -471,6 +471,9 @@ private:
   /// Processes SM6.0 quad-wide shuffle.
   /// Processes SM6.0 quad-wide shuffle.
   uint32_t processWaveQuadWideShuffle(const CallExpr *, hlsl::IntrinsicOp op);
   uint32_t processWaveQuadWideShuffle(const CallExpr *, hlsl::IntrinsicOp op);
 
 
+  /// Processes the NonUniformResourceIndex intrinsic function.
+  SpirvEvalInfo processIntrinsicNonUniformResourceIndex(const CallExpr *);
+
 private:
 private:
   /// Returns the <result-id> for constant value 0 of the given type.
   /// Returns the <result-id> for constant value 0 of the given type.
   uint32_t getValueZero(QualType type);
   uint32_t getValueZero(QualType type);
@@ -853,8 +856,8 @@ private:
   /// return a vec4. As a result, an extra processing step is necessary.
   /// return a vec4. As a result, an extra processing step is necessary.
   uint32_t createImageSample(QualType retType, uint32_t imageType,
   uint32_t createImageSample(QualType retType, uint32_t imageType,
                              uint32_t image, uint32_t sampler,
                              uint32_t image, uint32_t sampler,
-                             uint32_t coordinate, uint32_t compareVal,
-                             uint32_t bias, uint32_t lod,
+                             bool isNonUniform, uint32_t coordinate,
+                             uint32_t compareVal, uint32_t bias, uint32_t lod,
                              std::pair<uint32_t, uint32_t> grad,
                              std::pair<uint32_t, uint32_t> grad,
                              uint32_t constOffset, uint32_t varOffset,
                              uint32_t constOffset, uint32_t varOffset,
                              uint32_t constOffsets, uint32_t sample,
                              uint32_t constOffsets, uint32_t sample,
@@ -938,6 +941,13 @@ private:
   /// all 32-bit scalar constants will be translated into OpSpecConstant.
   /// all 32-bit scalar constants will be translated into OpSpecConstant.
   bool isSpecConstantMode;
   bool isSpecConstantMode;
 
 
+  /// Indicates that we have found a NonUniformResourceIndex call when
+  /// traversing.
+  /// This field is used to convery information in a bottom-up manner; if we
+  /// have something like `aResource[NonUniformResourceIndex(aIndex)]`, we need
+  /// to attach `aResource` with proper decorations.
+  bool foundNonUniformResourceIndex;
+
   /// Whether the translated SPIR-V binary needs legalization.
   /// Whether the translated SPIR-V binary needs legalization.
   ///
   ///
   /// The following cases will require legalization:
   /// The following cases will require legalization:

+ 10 - 1
tools/clang/lib/SPIRV/SpirvEvalInfo.h

@@ -93,6 +93,9 @@ public:
   inline SpirvEvalInfo &setRelaxedPrecision();
   inline SpirvEvalInfo &setRelaxedPrecision();
   bool isRelaxedPrecision() const { return isRelaxedPrecision_; }
   bool isRelaxedPrecision() const { return isRelaxedPrecision_; }
 
 
+  inline SpirvEvalInfo &setNonUniform(bool nu = true);
+  bool isNonUniform() const { return isNonUniform_; }
+
 private:
 private:
   uint32_t resultId;
   uint32_t resultId;
   /// Indicates whether this evaluation result contains alias variables
   /// Indicates whether this evaluation result contains alias variables
@@ -112,13 +115,14 @@ private:
   bool isConstant_;
   bool isConstant_;
   bool isSpecConstant_;
   bool isSpecConstant_;
   bool isRelaxedPrecision_;
   bool isRelaxedPrecision_;
+  bool isNonUniform_;
 };
 };
 
 
 SpirvEvalInfo::SpirvEvalInfo(uint32_t id)
 SpirvEvalInfo::SpirvEvalInfo(uint32_t id)
     : resultId(id), containsAlias(false),
     : resultId(id), containsAlias(false),
       storageClass(spv::StorageClass::Function), layoutRule(LayoutRule::Void),
       storageClass(spv::StorageClass::Function), layoutRule(LayoutRule::Void),
       isRValue_(false), isConstant_(false), isSpecConstant_(false),
       isRValue_(false), isConstant_(false), isSpecConstant_(false),
-      isRelaxedPrecision_(false) {}
+      isRelaxedPrecision_(false), isNonUniform_(false) {}
 
 
 SpirvEvalInfo &SpirvEvalInfo::setResultId(uint32_t id) {
 SpirvEvalInfo &SpirvEvalInfo::setResultId(uint32_t id) {
   resultId = id;
   resultId = id;
@@ -167,6 +171,11 @@ SpirvEvalInfo &SpirvEvalInfo::setRelaxedPrecision() {
   return *this;
   return *this;
 }
 }
 
 
+SpirvEvalInfo &SpirvEvalInfo::setNonUniform(bool nu) {
+  isNonUniform_ = nu;
+  return *this;
+}
+
 } // end namespace spirv
 } // end namespace spirv
 } // end namespace clang
 } // end namespace clang
 
 

+ 17 - 6
tools/clang/lib/SPIRV/TypeTranslator.cpp

@@ -621,11 +621,9 @@ uint32_t TypeTranslator::translateType(QualType type, LayoutRule rule) {
                                     decorations);
                                     decorations);
   }
   }
 
 
-  if (const auto *arrayType = astContext.getAsConstantArrayType(type)) {
+  // Array type
+  if (const auto *arrayType = astContext.getAsArrayType(type)) {
     const uint32_t elemType = translateType(arrayType->getElementType(), rule);
     const uint32_t elemType = translateType(arrayType->getElementType(), rule);
-    // TODO: handle extra large array size?
-    const auto size =
-        static_cast<uint32_t>(arrayType->getSize().getZExtValue());
 
 
     llvm::SmallVector<const Decoration *, 4> decorations;
     llvm::SmallVector<const Decoration *, 4> decorations;
     if (rule != LayoutRule::Void) {
     if (rule != LayoutRule::Void) {
@@ -635,8 +633,21 @@ uint32_t TypeTranslator::translateType(QualType type, LayoutRule rule) {
           Decoration::getArrayStride(*theBuilder.getSPIRVContext(), stride));
           Decoration::getArrayStride(*theBuilder.getSPIRVContext(), stride));
     }
     }
 
 
-    return theBuilder.getArrayType(elemType, theBuilder.getConstantUint32(size),
-                                   decorations);
+    if (const auto *caType = astContext.getAsConstantArrayType(type)) {
+      const auto size = static_cast<uint32_t>(caType->getSize().getZExtValue());
+      return theBuilder.getArrayType(
+          elemType, theBuilder.getConstantUint32(size), decorations);
+    } else {
+      assert(type->isIncompleteArrayType());
+      // Runtime arrays of resources needs additional capability.
+      if (hlsl::IsHLSLResourceType(arrayType->getElementType())) {
+        theBuilder.addExtension(Extension::EXT_descriptor_indexing,
+                                "runtime array of resources", {});
+        theBuilder.requireCapability(
+            spv::Capability::RuntimeDescriptorArrayEXT);
+      }
+      return theBuilder.getRuntimeArrayType(elemType, decorations);
+    }
   }
   }
 
 
   emitError("type %0 unimplemented") << type->getTypeClassName();
   emitError("type %0 unimplemented") << type->getTypeClassName();

+ 142 - 0
tools/clang/test/CodeGenSPIRV/intrinsics.non-uniform-resource-index.hlsl

@@ -0,0 +1,142 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// CHECK: OpCapability ShaderNonUniformEXT
+// CHECK: OpCapability SampledImageArrayNonUniformIndexingEXT
+// CHECK: OpCapability StorageImageArrayNonUniformIndexingEXT
+// CHECK: OpCapability UniformTexelBufferArrayNonUniformIndexingEXT
+// CHECK: OpCapability StorageTexelBufferArrayNonUniformIndexingEXT
+// CHECK: OpCapability InputAttachmentArrayNonUniformIndexingEXT
+
+// CHECK: OpExtension "SPV_EXT_descriptor_indexing"
+
+// CHECK: OpDecorate [[nu1:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu2:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu3:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu4:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu5:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu6:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu7:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu8:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu9:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu10:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu11:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu12:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu13:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu14:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu15:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu16:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu17:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu18:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu19:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu20:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu21:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu22:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu23:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu24:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu25:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu26:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu27:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu28:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu29:%\d+]] NonUniformEXT
+
+// CHECK: OpDecorate [[nu30:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu31:%\d+]] NonUniformEXT
+// CHECK: OpDecorate [[nu32:%\d+]] NonUniformEXT
+
+Texture2D           gTextures[32];
+SamplerState        gSamplers[];
+RWTexture2D<float4> gRWTextures[32];
+Buffer<float4>      gBuffers[];
+RWBuffer<uint>      gRWBuffers[32];
+SubpassInput        gSubpassInputs[32];
+
+float4 main(uint index : A, float2 loc : B, int2 offset : C) : SV_Target {
+// CHECK: [[nu1]] = OpLoad %uint %index
+// CHECK: [[nu2]] = OpLoad %type_2d_image
+// CHECK: [[nu3]] = OpIAdd %uint {{%\d+}} %uint_1
+// CHECK: [[nu4]] = OpLoad %type_sampler
+// CHECK: [[nu5]] = OpSampledImage %type_sampled_image
+// CHECK:           OpImageSampleImplicitLod
+    float4 v1 = gTextures[NonUniformResourceIndex(index)].Sample(
+        gSamplers[NonUniformResourceIndex(index + 1)], loc);
+
+// CHECK: [[nu6]] = OpLoad %uint %index
+// CHECK:           OpIAdd %uint {{%\d+}} %uint_1
+// CHECK: [[nu7]] = OpLoad %type_sampler
+// CHECK: [[nu8]] = OpSampledImage %type_sampled_image
+// CHECK:           OpImageSampleImplicitLod
+    float4 v2 = gTextures[0].Sample(
+        gSamplers[NonUniformResourceIndex(index++)], loc);
+
+// CHECK:            OpLoad %uint %index
+// CHECK:            OpISub %uint {{%\d+}} %uint_1
+// CHECK:  [[nu9]] = OpLoad %uint %index
+// CHECK: [[nu10]] = OpLoad %type_2d_image
+// CHECK: [[nu11]] = OpSampledImage %type_sampled_image
+// CHECK:            OpImageSampleImplicitLod
+    float4 v3 = gTextures[NonUniformResourceIndex(--index)].Sample(
+        gSamplers[0], loc);
+
+// CHECK: [[nu12]] = OpIMul %uint
+// CHECK: [[nu13]] = OpLoad %type_2d_image_0
+// CHECK:            OpImageRead
+    float4 v4 = gRWTextures[NonUniformResourceIndex(index * index)].Load(loc);
+
+// CHECK: [[nu14]] = OpLoad %uint %index
+// CHECK:            OpUMod %uint {{%\d+}} %uint_3
+// CHECK: [[nu15]] = OpLoad %type_2d_image_0
+// CHECK:            OpImageWrite
+    gRWTextures[NonUniformResourceIndex(index) % 3][loc] = 4;
+
+// CHECK: [[nu16]] = OpLoad %uint %index
+// CHECK:            OpLoad %uint %index
+// CHECK: [[nu17]] = OpLoad %type_buffer_image
+// CHECK:            OpImageFetch
+    float4 v5 = gBuffers[NonUniformResourceIndex(index) * index][5];
+
+// CHECK: [[nu18]] = OpLoad %uint %index
+// CHECK: [[nu19]] = OpLoad %type_buffer_image_0
+// CHECK:            OpImageRead
+    float4 v6 = gRWBuffers[NonUniformResourceIndex(index)].Load(6);
+
+// CHECK: [[nu20]] = OpLoad %uint %index
+// CHECK: [[nu21]] = OpLoad %type_buffer_image_0
+// CHECK:            OpImageWrite
+    gRWBuffers[NonUniformResourceIndex(index)][8] = 9;
+
+// CHECK: [[nu22]] = OpLoad %uint %index
+// CHECK: [[nu23]] = OpLoad %uint %index
+// CHECK: [[nu24]] = OpImageTexelPointer %_ptr_Image_uint {{%\d+}} %uint_10 %uint_0
+// CHECK:            OpAtomicIAdd
+    uint old = 0;
+    InterlockedAdd(gRWBuffers[NonUniformResourceIndex(index) * NonUniformResourceIndex(index)][10], 1, old);
+
+// CHECK: [[nu25]] = OpLoad %uint %index
+// CHECK: [[nu26]] = OpLoad %type_subpass_image
+// CHECK:            OpImageRead
+    float4 v7 = gSubpassInputs[NonUniformResourceIndex(index)].SubpassLoad();
+
+// CHECK: [[nu27]] = OpLoad %uint %index
+// CHECK: [[nu28]] = OpLoad %type_2d_image
+// CHECK: [[nu29]] = OpSampledImage %type_sampled_image
+// CHECK:            OpImageGather
+    float4 v8 = gTextures[NonUniformResourceIndex(index)].Gather(gSamplers[0], loc, offset);
+
+// CHECK: [[nu30]] = OpLoad %uint %index
+// CHECK: [[nu31]] = OpLoad %type_2d_image
+// CHECK: [[nu32]] = OpSampledImage %type_sampled_image
+// CHECK:            OpImageQueryLod
+    float  v9 = gTextures[NonUniformResourceIndex(index)].CalculateLevelOfDetail(gSamplers[0], 0.5);
+
+    return v1 + v2 + v3 + v4 + v5 + v6 + v7;
+}

+ 31 - 0
tools/clang/test/CodeGenSPIRV/type.runtime-array.hlsl

@@ -0,0 +1,31 @@
+// Run: %dxc -T ps_6_0 -E main
+
+// CHECK: OpCapability RuntimeDescriptorArrayEXT
+
+// CHECK: OpExtension "SPV_EXT_descriptor_indexing"
+
+struct S {
+    float4 a;
+    float3 b;
+    float  c;
+};
+
+// CHECK: %type_2d_image = OpTypeImage %float 2D 0 0 0 1 Unknow
+// CHECK: %_runtimearr_type_2d_image = OpTypeRuntimeArray %type_2d_image
+
+// CHECK: %type_sampler = OpTypeSampler
+// CHECK: %_runtimearr_type_sampler = OpTypeRuntimeArray %type_sampler
+
+// CHECK: %type_ConstantBuffer_S = OpTypeStruct %v4float %v3float %float
+// CHECK: %_runtimearr_type_ConstantBuffer_S = OpTypeRuntimeArray %type_ConstantBuffer_S
+
+// CHECK: %MyTextures = OpVariable %_ptr_UniformConstant__runtimearr_type_2d_image UniformConstant
+Texture2D<float4> MyTextures[];
+// CHECK: %MySamplers = OpVariable %_ptr_UniformConstant__runtimearr_type_sampler UniformConstant
+SamplerState      MySamplers[];
+// CHECK: %MyCBuffers = OpVariable %_ptr_Uniform__runtimearr_type_ConstantBuffer_S Uniform
+ConstantBuffer<S> MyCBuffers[];
+
+float4 main(float2 loc : LOC) : SV_Target {
+    return MyTextures[0].Sample(MySamplers[0], loc) + MyCBuffers[0].a;
+}

+ 4 - 0
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -54,6 +54,7 @@ TEST_F(FileTest, MatrixTypesMajornessZpc) {
 TEST_F(FileTest, StructTypes) { runFileTest("type.struct.hlsl"); }
 TEST_F(FileTest, StructTypes) { runFileTest("type.struct.hlsl"); }
 TEST_F(FileTest, ClassTypes) { runFileTest("type.class.hlsl"); }
 TEST_F(FileTest, ClassTypes) { runFileTest("type.class.hlsl"); }
 TEST_F(FileTest, ArrayTypes) { runFileTest("type.array.hlsl"); }
 TEST_F(FileTest, ArrayTypes) { runFileTest("type.array.hlsl"); }
+TEST_F(FileTest, RuntimeArrayTypes) { runFileTest("type.runtime-array.hlsl"); }
 TEST_F(FileTest, TypedefTypes) { runFileTest("type.typedef.hlsl"); }
 TEST_F(FileTest, TypedefTypes) { runFileTest("type.typedef.hlsl"); }
 TEST_F(FileTest, SamplerTypes) { runFileTest("type.sampler.hlsl"); }
 TEST_F(FileTest, SamplerTypes) { runFileTest("type.sampler.hlsl"); }
 TEST_F(FileTest, TextureTypes) { runFileTest("type.texture.hlsl"); }
 TEST_F(FileTest, TextureTypes) { runFileTest("type.texture.hlsl"); }
@@ -970,6 +971,9 @@ TEST_F(FileTest, IntrinsicsGetRenderTargetSamplePosition) {
   runFileTest("intrinsics.get-render-target-sample-position.hlsl",
   runFileTest("intrinsics.get-render-target-sample-position.hlsl",
               Expect::Failure);
               Expect::Failure);
 }
 }
+TEST_F(FileTest, IntrinsicsNonUniformResourceIndex) {
+  runFileTest("intrinsics.non-uniform-resource-index.hlsl");
+}
 
 
 // For attributes
 // For attributes
 TEST_F(FileTest, AttributeEarlyDepthStencil) {
 TEST_F(FileTest, AttributeEarlyDepthStencil) {