Explorar o código

[spirv] Support Hull shader translation (#692)

Ehsan %!s(int64=8) %!d(string=hai) anos
pai
achega
e508374632
Modificáronse 28 ficheiros con 1354 adicións e 61 borrados
  1. 3 0
      tools/clang/include/clang/SPIRV/ModuleBuilder.h
  2. 125 51
      tools/clang/lib/SPIRV/DeclResultIdMapper.cpp
  3. 26 3
      tools/clang/lib/SPIRV/DeclResultIdMapper.h
  4. 7 0
      tools/clang/lib/SPIRV/ModuleBuilder.cpp
  5. 263 7
      tools/clang/lib/SPIRV/SPIRVEmitter.cpp
  6. 38 0
      tools/clang/lib/SPIRV/SPIRVEmitter.h
  7. 32 0
      tools/clang/lib/SPIRV/TypeTranslator.cpp
  8. 6 0
      tools/clang/lib/SPIRV/TypeTranslator.h
  9. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.domain.isoline.hlsl
  10. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.domain.quad.hlsl
  11. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.domain.tri.hlsl
  12. 16 0
      tools/clang/test/CodeGenSPIRV/attribute.outputcontrolpoints.hlsl
  13. 16 0
      tools/clang/test/CodeGenSPIRV/attribute.outputtopology.point.hlsl
  14. 16 0
      tools/clang/test/CodeGenSPIRV/attribute.outputtopology.triangle-ccw.hlsl
  15. 16 0
      tools/clang/test/CodeGenSPIRV/attribute.outputtopology.triangle-cw.hlsl
  16. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.partitioning.fractional-even.hlsl
  17. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.partitioning.fractional-odd.hlsl
  18. 17 0
      tools/clang/test/CodeGenSPIRV/attribute.partitioning.integer.hlsl
  19. 250 0
      tools/clang/test/CodeGenSPIRV/bezier.hull.hlsl2spv
  20. 43 0
      tools/clang/test/CodeGenSPIRV/bezier_common_hull.hlsl
  21. 70 0
      tools/clang/test/CodeGenSPIRV/hull.output-vars.hlsl
  22. 45 0
      tools/clang/test/CodeGenSPIRV/hull.pcf.input-patch.hlsl
  23. 53 0
      tools/clang/test/CodeGenSPIRV/hull.pcf.output-patch.hlsl
  24. 49 0
      tools/clang/test/CodeGenSPIRV/hull.pcf.primitive-id-2.hlsl
  25. 49 0
      tools/clang/test/CodeGenSPIRV/hull.pcf.primitive-id.hlsl
  26. 33 0
      tools/clang/test/CodeGenSPIRV/hull.pcf.void.hlsl
  27. 41 0
      tools/clang/test/CodeGenSPIRV/hull.structure.hlsl
  28. 55 0
      tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

+ 3 - 0
tools/clang/include/clang/SPIRV/ModuleBuilder.h

@@ -243,6 +243,9 @@ public:
   uint32_t createExtInst(uint32_t resultType, uint32_t setId, uint32_t instId,
                          llvm::ArrayRef<uint32_t> operands);
 
+  /// \brief Creates an OpControlBarrier instruction with the given flags.
+  void createControlBarrier(uint32_t exec, uint32_t memory, uint32_t semantics);
+
   // === SPIR-V Module Structure ===
 
   inline void requireCapability(spv::Capability);

+ 125 - 51
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -47,13 +47,16 @@ const hlsl::RegisterAssignment *getResourceBinding(const NamedDecl *decl) {
 } // anonymous namespace
 
 bool DeclResultIdMapper::createStageOutputVar(const DeclaratorDecl *decl,
-                                              uint32_t storedValue) {
-  return createStageVars(decl, &storedValue, false, "out.var");
+                                              uint32_t storedValue,
+                                              bool isPatchConstant) {
+  return createStageVars(decl, &storedValue, false, "out.var", isPatchConstant);
 }
 
 bool DeclResultIdMapper::createStageInputVar(const ParmVarDecl *paramDecl,
-                                             uint32_t *loadedValue) {
-  return createStageVars(paramDecl, loadedValue, true, "in.var");
+                                             uint32_t *loadedValue,
+                                             bool isPatchConstant) {
+  return createStageVars(paramDecl, loadedValue, true, "in.var",
+                         isPatchConstant);
 }
 
 const DeclResultIdMapper::DeclSpirvInfo *
@@ -369,8 +372,8 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
 
   // Returns false if the given StageVar is an input/output variable without
   // explicit location assignment. Otherwise, returns true.
-  const auto locAssigned = [forInput](const StageVar &v) {
-    if (forInput ? v.getSigPoint()->IsInput() : v.getSigPoint()->IsOutput())
+  const auto locAssigned = [forInput, this](const StageVar &v) {
+    if (forInput == isInputStorageClass(v))
       // No need to assign location for builtins. Treat as assigned.
       return v.isSpirvBuitin() || v.getLocationAttr() != nullptr;
     // For the ones we don't care, treat as assigned.
@@ -385,8 +388,7 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
 
     for (const auto &var : stageVars) {
       // Skip those stage variables we are not handling for this call
-      if ((forInput ? !var.getSigPoint()->IsInput()
-                    : !var.getSigPoint()->IsOutput()))
+      if (forInput != isInputStorageClass(var))
         continue;
 
       // Skip builtins
@@ -423,8 +425,7 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
   LocationSet locSet;
 
   for (const auto &var : stageVars) {
-    if ((forInput ? !var.getSigPoint()->IsInput()
-                  : !var.getSigPoint()->IsOutput()))
+    if (forInput != isInputStorageClass(var))
       continue;
 
     if (!var.isSpirvBuitin()) {
@@ -440,7 +441,8 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
       // Only SV_Target, SV_Depth, SV_DepthLessEqual, SV_DepthGreaterEqual,
       // SV_StencilRef, SV_Coverage are allowed in the pixel shader.
       // Arbitrary semantics are disallowed in pixel shader.
-      if (var.getSemantic()->GetKind() == hlsl::Semantic::Kind::Target) {
+      if (var.getSemantic() &&
+          var.getSemantic()->GetKind() == hlsl::Semantic::Kind::Target) {
         theBuilder.decorateLocation(var.getSpirvId(), var.getSemanticIndex());
         locSet.useLoc(var.getSemanticIndex());
       } else {
@@ -515,9 +517,30 @@ DeclResultIdMapper::getFnParamOrRetType(const DeclaratorDecl *decl) const {
   return decl->getType();
 }
 
+uint32_t DeclResultIdMapper::createStageVarWithoutSemantics(
+    bool isInput, uint32_t typeId, const llvm::StringRef name,
+    const clang::VKLocationAttr *loc) {
+  const hlsl::SigPoint *sigPoint = hlsl::SigPoint::GetSigPoint(
+      hlsl::SigPointFromInputQual(isInput ? hlsl::DxilParamInputQual::In
+                                          : hlsl::DxilParamInputQual::Out,
+                                  shaderModel.GetKind(), /*isPC*/ false));
+  StageVar stageVar(sigPoint, name, nullptr, 0, typeId);
+  const llvm::Twine fullName = (isInput ? "in.var." : "out.var.") + name;
+  const spv::StorageClass sc =
+      isInput ? spv::StorageClass::Input : spv::StorageClass::Output;
+  const uint32_t varId = theBuilder.addStageIOVar(typeId, sc, fullName.str());
+  if (varId == 0)
+    return 0;
+  stageVar.setSpirvId(varId);
+  stageVar.setLocationAttr(loc);
+  stageVars.push_back(stageVar);
+  return varId;
+}
+
 bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
                                          uint32_t *value, bool asInput,
-                                         const llvm::Twine &namePrefix) {
+                                         const llvm::Twine &namePrefix,
+                                         bool isPatchConstant) {
   QualType type = getFnParamOrRetType(decl);
   if (type->isVoidType()) {
     // No stage variables will be created for void type.
@@ -525,32 +548,28 @@ bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
   }
   const uint32_t typeId = typeTranslator.translateType(type);
 
-  const llvm::StringRef semanticStr = getStageVarSemantic(decl);
+  llvm::StringRef semanticStr = getStageVarSemantic(decl);
   if (!semanticStr.empty()) {
     // Found semantic attached directly to this Decl. This means we need to
     // map this decl to a single stage variable.
 
     const hlsl::DxilParamInputQual qual =
         asInput ? hlsl::DxilParamInputQual::In : hlsl::DxilParamInputQual::Out;
-
-    // TODO: use the correct isPC value when supporting patch constant function
-    // in hull shader
     const hlsl::SigPoint *sigPoint =
         hlsl::SigPoint::GetSigPoint(hlsl::SigPointFromInputQual(
-            qual, shaderModel.GetKind(), /*isPC*/ false));
+            qual, shaderModel.GetKind(), isPatchConstant));
 
     llvm::StringRef semanticName;
     uint32_t semanticIndex = 0;
     hlsl::Semantic::DecomposeNameAndIndex(semanticStr, &semanticName,
                                           &semanticIndex);
-
     const auto *semantic = hlsl::Semantic::GetByName(semanticName);
 
     // Error out when the given semantic is invalid in this shader model
     if (hlsl::SigPoint::GetInterpretation(
-            semantic->GetKind(), sigPoint->GetKind(), shaderModel.GetMajor(),
-            shaderModel.GetMinor()) ==
-        hlsl::DXIL::SemanticInterpretationKind::NA) {
+                             semantic->GetKind(), sigPoint->GetKind(),
+                             shaderModel.GetMajor(), shaderModel.GetMinor()) ==
+                             hlsl::DXIL::SemanticInterpretationKind::NA) {
       emitError("invalid semantic %0 for shader module %1")
           << semanticStr << shaderModel.GetName();
       return false;
@@ -597,7 +616,6 @@ bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
 
     stageVar.setSpirvId(varId);
     stageVar.setLocationAttr(decl->getAttr<VKLocationAttr>());
-
     stageVars.push_back(stageVar);
 
     if (asInput) {
@@ -619,7 +637,8 @@ bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
       llvm::SmallVector<uint32_t, 4> subValues;
       for (const auto *field : structDecl->fields()) {
         uint32_t subValue = 0;
-        if (!createStageVars(field, &subValue, true, namePrefix))
+        if (!createStageVars(field, &subValue, true, namePrefix,
+                             isPatchConstant))
           return false;
         subValues.push_back(subValue);
       }
@@ -632,7 +651,8 @@ bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
             typeTranslator.translateType(field->getType());
         uint32_t subValue = theBuilder.createCompositeExtract(
             fieldType, *value, {field->getFieldIndex()});
-        if (!createStageVars(field, &subValue, false, namePrefix))
+        if (!createStageVars(field, &subValue, false, namePrefix,
+                             isPatchConstant))
           return false;
       }
     }
@@ -644,21 +664,19 @@ bool DeclResultIdMapper::createStageVars(const DeclaratorDecl *decl,
 uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
                                                  const llvm::Twine &name) {
   using spv::BuiltIn;
-
+  const auto sigPoint = stageVar->getSigPoint();
   const auto semanticKind = stageVar->getSemantic()->GetKind();
-  const auto sigPointKind = stageVar->getSigPoint()->GetKind();
+  const auto sigPointKind = sigPoint->GetKind();
   const uint32_t type = stageVar->getSpirvTypeId();
 
+  spv::StorageClass sc = getStorageClassForSigPoint(sigPoint);
+  if (sc == spv::StorageClass::Max)
+    return 0;
+  stageVar->setStorageClass(sc);
+
   // The following translation assumes that semantic validity in the current
   // shader model is already checked, so it only covers valid SigPoints for
   // each semantic.
-
-  // TODO: case for patch constant
-  const auto sc = stageVar->getSigPoint()->IsInput()
-                      ? spv::StorageClass::Input
-                      : spv::StorageClass::Output;
-  stageVar->setStorageClass(sc);
-
   switch (semanticKind) {
   // According to DXIL spec, the Position SV can be used by all SigPoints
   // other than PCIn, HSIn, GSIn, PSOut, CSIn.
@@ -676,7 +694,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
       return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::FragCoord);
     default:
       emitError("semantic Position for SigPoint %0 unimplemented yet")
-          << stageVar->getSigPoint()->GetName();
+          << sigPoint->GetName();
       break;
     }
   }
@@ -699,7 +717,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
       return theBuilder.addStageIOVar(type, sc, name.str());
     default:
       emitError("semantic InstanceID for SigPoint %0 unimplemented yet")
-          << stageVar->getSigPoint()->GetName();
+          << sigPoint->GetName();
       break;
     }
   }
@@ -727,7 +745,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
       return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::FrontFacing);
     default:
       emitError("semantic IsFrontFace for SigPoint %0 unimplemented yet")
-          << stageVar->getSigPoint()->GetName();
+          << sigPoint->GetName();
       break;
     }
   }
@@ -742,33 +760,38 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
     // TODO: patch constant function in hull shader
   }
   case hlsl::Semantic::Kind::DispatchThreadID: {
-    // DispatchThreadID semantic is only valid for compute shaders, and it is
-    // always an input.
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVar(type, spv::StorageClass::Input,
-                                         BuiltIn::GlobalInvocationId);
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::GlobalInvocationId);
   }
   case hlsl::Semantic::Kind::GroupID: {
-    // GroupID semantic is only valid for compute shaders, and it is always an
-    // input.
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVar(type, spv::StorageClass::Input,
-                                         BuiltIn::WorkgroupId);
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::WorkgroupId);
   }
   case hlsl::Semantic::Kind::GroupThreadID: {
-    // GroupThreadID semantic is only valid for compute shaders, and it is
-    // always an input.
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVar(type, spv::StorageClass::Input,
-                                         BuiltIn::LocalInvocationId);
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::LocalInvocationId);
   }
   case hlsl::Semantic::Kind::GroupIndex: {
-    // GroupIndex semantic is only valid for compute shaders, and it is
-    // always an input.
     stageVar->setIsSpirvBuiltin();
-    return theBuilder.addStageBuiltinVar(type, spv::StorageClass::Input,
+    return theBuilder.addStageBuiltinVar(type, sc,
                                          BuiltIn::LocalInvocationIndex);
   }
+  case hlsl::Semantic::Kind::OutputControlPointID: {
+    stageVar->setIsSpirvBuiltin();
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::InvocationId);
+  }
+  case hlsl::Semantic::Kind::PrimitiveID: {
+    stageVar->setIsSpirvBuiltin();
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::PrimitiveId);
+  }
+  case hlsl::Semantic::Kind::TessFactor: {
+    stageVar->setIsSpirvBuiltin();
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::TessLevelOuter);
+  }
+  case hlsl::Semantic::Kind::InsideTessFactor: {
+    stageVar->setIsSpirvBuiltin();
+    return theBuilder.addStageBuiltinVar(type, sc, BuiltIn::TessLevelInner);
+  }
   default:
     emitError("semantic %0 unimplemented yet")
         << stageVar->getSemantic()->GetName();
@@ -778,5 +801,56 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
   return 0;
 }
 
+spv::StorageClass
+DeclResultIdMapper::getStorageClassForSigPoint(const hlsl::SigPoint *sigPoint) {
+  // This translation is done based on the HLSL reference (see docs/dxil.rst).
+  const auto sigPointKind = sigPoint->GetKind();
+  const auto signatureKind = sigPoint->GetSignatureKind();
+  spv::StorageClass sc = spv::StorageClass::Max;
+  switch (signatureKind) {
+  case hlsl::DXIL::SignatureKind::Input:
+    sc = spv::StorageClass::Input;
+    break;
+  case hlsl::DXIL::SignatureKind::Output:
+    sc = spv::StorageClass::Output;
+    break;
+  case hlsl::DXIL::SignatureKind::Invalid: {
+    // There are some special cases in HLSL (See docs/dxil.rst):
+    // SignatureKind is "invalid" for PCIn, HSIn, GSIn, and CSIn.
+    switch (sigPointKind) {
+    case hlsl::DXIL::SigPointKind::PCIn:
+    case hlsl::DXIL::SigPointKind::HSIn:
+    case hlsl::DXIL::SigPointKind::GSIn:
+    case hlsl::DXIL::SigPointKind::CSIn:
+      sc = spv::StorageClass::Input;
+      break;
+    default:
+      emitError("Found invalid SigPoint kind for a semantic.");
+    }
+    break;
+  }
+  case hlsl::DXIL::SignatureKind::PatchConstant: {
+    // There are some special cases in HLSL (See docs/dxil.rst):
+    // SignatureKind is "PatchConstant" for PCOut and DSIn.
+    switch (sigPointKind) {
+    case hlsl::DXIL::SigPointKind::PCOut:
+      // Patch Constant Output (Output of Hull which is passed to Domain).
+      sc = spv::StorageClass::Output;
+      break;
+    case hlsl::DXIL::SigPointKind::DSIn:
+      // Domain Shader regular input - Patch Constant data plus system values.
+      sc = spv::StorageClass::Input;
+      break;
+    default:
+      emitError("Found invalid SigPoint kind for a semantic.");
+    }
+    break;
+  }
+  default:
+    emitError("Found invalid SignatureKind for semantic.");
+  }
+  return sc;
+}
+
 } // end namespace spirv
 } // end namespace clang

+ 26 - 3
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -126,13 +126,25 @@ public:
   /// true on success. SPIR-V instructions will also be generated to update the
   /// contents of the output variables by extracting sub-values from the given
   /// storedValue.
-  bool createStageOutputVar(const DeclaratorDecl *decl, uint32_t storedValue);
+  bool createStageOutputVar(const DeclaratorDecl *decl, uint32_t storedValue,
+                            bool isPatchConstant);
 
   /// \brief Creates the stage input variables by parsing the semantics attached
   /// to the given function's parameter and returns true on success. SPIR-V
   /// instructions will also be generated to load the contents from the input
   /// variables and composite them into one and write to *loadedValue.
-  bool createStageInputVar(const ParmVarDecl *paramDecl, uint32_t *loadedValue);
+  bool createStageInputVar(const ParmVarDecl *paramDecl, uint32_t *loadedValue,
+                           bool isPatchConstant);
+
+  /// \brief Creates an input/output stage variable which does not have any
+  /// semantics (such as InputPatch/OutputPatch in Hull shaders). This method
+  /// does not create a Load/Store from/to the created stage variable and leaves
+  /// it to the caller to do so as they see fit, because it is possible that the
+  /// stage variable may have to be accessed differently (using OpAccessChain
+  /// for example).
+  uint32_t createStageVarWithoutSemantics(bool isInput, uint32_t typeId,
+                                          const llvm::StringRef name,
+                                          const clang::VKLocationAttr *loc);
 
   /// \brief Creates a function-scope paramter in the current function and
   /// returns its <result-id>.
@@ -270,7 +282,8 @@ private:
   ///
   /// Assumes the decl has semantic attached to itself or to its fields.
   bool createStageVars(const DeclaratorDecl *decl, uint32_t *value,
-                       bool asInput, const llvm::Twine &namePrefix);
+                       bool asInput, const llvm::Twine &namePrefix,
+                       bool isPatchConstant);
 
   /// Creates the SPIR-V variable instruction for the given StageVar and returns
   /// the <result-id>. Also sets whether the StageVar is a SPIR-V builtin and
@@ -278,6 +291,16 @@ private:
   /// creating a stage input/output variable.
   uint32_t createSpirvStageVar(StageVar *, const llvm::Twine &name);
 
+  /// Returns the proper SPIR-V storage class (Input or Output) for the given
+  /// SigPoint.
+  spv::StorageClass getStorageClassForSigPoint(const hlsl::SigPoint *);
+
+  /// Returns true if the given SPIR-V stage variable has Input storage class.
+  inline bool isInputStorageClass(const StageVar &v) {
+    return getStorageClassForSigPoint(v.getSigPoint()) ==
+           spv::StorageClass::Input;
+  }
+
 private:
   const hlsl::ShaderModel &shaderModel;
   ModuleBuilder &theBuilder;

+ 7 - 0
tools/clang/lib/SPIRV/ModuleBuilder.cpp

@@ -541,6 +541,13 @@ uint32_t ModuleBuilder::createExtInst(uint32_t resultType, uint32_t setId,
   return resultId;
 }
 
+void ModuleBuilder::createControlBarrier(uint32_t execution, uint32_t memory,
+                                         uint32_t semantics) {
+  assert(insertPoint && "null insert point");
+  instBuilder.opControlBarrier(execution, memory, semantics).x();
+  insertPoint->appendInstruction(std::move(constructSite));
+}
+
 void ModuleBuilder::addExecutionMode(uint32_t entryPointId,
                                      spv::ExecutionMode em,
                                      llvm::ArrayRef<uint32_t> params) {

+ 263 - 7
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -24,6 +24,31 @@ namespace spirv {
 
 namespace {
 
+// Returns true if the given decl has the given semantic.
+bool hasSemantic(const DeclaratorDecl *decl,
+                 hlsl::DXIL::SemanticKind semanticKind) {
+  using namespace hlsl;
+  for (auto *annotation : decl->getUnusualAnnotations()) {
+    if (auto *semanticDecl = dyn_cast<SemanticDecl>(annotation)) {
+      llvm::StringRef semanticName;
+      uint32_t semanticIndex = 0;
+      Semantic::DecomposeNameAndIndex(semanticDecl->SemanticName, &semanticName,
+                                      &semanticIndex);
+      const auto *semantic = Semantic::GetByName(semanticName);
+      if (semantic->GetKind() == semanticKind)
+        return true;
+    }
+  }
+  return false;
+}
+
+bool patchConstFuncTakesHullOutputPatch(FunctionDecl *pcf) {
+  for (const auto *param : pcf->parameters())
+    if (TypeTranslator::isOutputPatch(param->getType()))
+      return true;
+  return false;
+}
+
 // TODO: Maybe we should move these type probing functions to TypeTranslator.
 
 /// Returns true if the two types are the same scalar or vector type.
@@ -244,6 +269,9 @@ void SPIRVEmitter::HandleTranslationUnit(ASTContext &context) {
       if (funcDecl->getName() == entryFunctionName) {
         workQueue.insert(funcDecl);
       }
+      if (context.IsPatchConstantFunctionDecl(funcDecl)) {
+        patchConstFunc = funcDecl;
+      }
     } else if (auto *varDecl = dyn_cast<VarDecl>(decl)) {
       if (isa<HLSLBufferDecl>(varDecl->getDeclContext())) {
         // This is a VarDecl of a ConstantBuffer/TextureBuffer type.
@@ -4835,7 +4863,6 @@ SPIRVEmitter::getSpirvShaderStage(const hlsl::ShaderModel &model) {
 void SPIRVEmitter::AddRequiredCapabilitiesForShaderModel() {
   if (shaderModel.IsHS() || shaderModel.IsDS()) {
     theBuilder.requireCapability(spv::Capability::Tessellation);
-    emitError("Tasselation shaders are currently not supported.");
   } else if (shaderModel.IsGS()) {
     theBuilder.requireCapability(spv::Capability::Geometry);
     emitError("Geometry shaders are currently not supported.");
@@ -4851,8 +4878,79 @@ void SPIRVEmitter::AddExecutionModeForEntryPoint(uint32_t entryPointId) {
   }
 }
 
+bool SPIRVEmitter::processHullShaderAttributes(
+    const FunctionDecl *decl, uint32_t *numOutputControlPoints) {
+  assert(shaderModel.IsHS());
+  using namespace spv;
+  if (auto *domain = decl->getAttr<HLSLDomainAttr>()) {
+    const auto domainType = domain->getDomainType().lower();
+    const ExecutionMode hsExecMode =
+        llvm::StringSwitch<ExecutionMode>(domainType)
+            .Case("tri", ExecutionMode::Triangles)
+            .Case("quad", ExecutionMode::Quads)
+            .Case("isoline", ExecutionMode::Isolines)
+            .Default(ExecutionMode::Max);
+    if (hsExecMode == ExecutionMode::Max) {
+      emitError("unknown domain type in hull shader", decl->getLocation());
+      return false;
+    }
+    theBuilder.addExecutionMode(entryFunctionId, hsExecMode, {});
+  }
+  if (auto *partitioning = decl->getAttr<HLSLPartitioningAttr>()) {
+    // TODO: Could not find an equivalent of "pow2" partitioning scheme in
+    // SPIR-V.
+    const auto scheme = partitioning->getScheme().lower();
+    const ExecutionMode hsExecMode =
+        llvm::StringSwitch<ExecutionMode>(scheme)
+            .Case("fractional_even", ExecutionMode::SpacingFractionalEven)
+            .Case("fractional_odd", ExecutionMode::SpacingFractionalOdd)
+            .Case("integer", ExecutionMode::SpacingEqual)
+            .Default(ExecutionMode::Max);
+    if (hsExecMode == ExecutionMode::Max) {
+      emitError("unknown partitioning scheme in hull shader",
+                decl->getLocation());
+      return false;
+    }
+    theBuilder.addExecutionMode(entryFunctionId, hsExecMode, {});
+  }
+  if (auto *outputTopology = decl->getAttr<HLSLOutputTopologyAttr>()) {
+    const auto topology = outputTopology->getTopology().lower();
+    const ExecutionMode hsExecMode =
+        llvm::StringSwitch<ExecutionMode>(topology)
+            .Case("point", ExecutionMode::PointMode)
+            .Case("triangle_cw", ExecutionMode::VertexOrderCw)
+            .Case("triangle_ccw", ExecutionMode::VertexOrderCcw)
+            .Default(ExecutionMode::Max);
+    // TODO: There is no SPIR-V equivalent for "line" topology. Is it the
+    // default?
+    if (topology != "line") {
+      if (hsExecMode != spv::ExecutionMode::Max) {
+        theBuilder.addExecutionMode(entryFunctionId, hsExecMode, {});
+      } else {
+        emitError("unknown output topology in hull shader",
+                  decl->getLocation());
+        return false;
+      }
+    }
+  }
+  if (auto *controlPoints = decl->getAttr<HLSLOutputControlPointsAttr>()) {
+    *numOutputControlPoints = controlPoints->getCount();
+    theBuilder.addExecutionMode(entryFunctionId,
+                                spv::ExecutionMode::OutputVertices,
+                                {*numOutputControlPoints});
+  }
+
+  return true;
+}
+
 bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
                                             const uint32_t entryFuncId) {
+  // These are going to be used for Hull shaders only.
+  uint32_t numOutputControlPoints = 0;
+  uint32_t outputControlPointIdVal = 0;
+  uint32_t primitiveIdVar = 0;
+  uint32_t hullMainInputPatchParam = 0;
+
   // Construct the wrapper function signature.
   const uint32_t voidType = theBuilder.getVoidType();
   const uint32_t funcType = theBuilder.getFunctionType(voidType, {});
@@ -4878,6 +4976,9 @@ bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
       theBuilder.addExecutionMode(entryFunctionId,
                                   spv::ExecutionMode::LocalSize, {1, 1, 1});
     }
+  } else if (shaderModel.IsHS()) {
+    if (!processHullShaderAttributes(decl, &numOutputControlPoints))
+      return false;
   }
 
   // The entry basic block.
@@ -4902,10 +5003,24 @@ bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
     // initialize the corresponding temporary variable
     if (!param->getAttr<HLSLOutAttr>()) {
       uint32_t loadedValue = 0;
-      if (!declIdMapper.createStageInputVar(param, &loadedValue))
+      if (TypeTranslator::isInputPatch(param->getType())) {
+        const uint32_t hullInputPatchId =
+            declIdMapper.createStageVarWithoutSemantics(
+                /*isInput*/ true, typeId, "hullEntryPointInput",
+                decl->getAttr<VKLocationAttr>());
+        loadedValue = theBuilder.createLoad(typeId, hullInputPatchId);
+        hullMainInputPatchParam = tempVar;
+      } else if (!declIdMapper.createStageInputVar(param, &loadedValue,
+                                                   /*isPC*/ false)) {
         return false;
+      }
 
       theBuilder.createStore(tempVar, loadedValue);
+
+      if (hasSemantic(param, hlsl::DXIL::SemanticKind::OutputControlPointID))
+        outputControlPointIdVal = loadedValue;
+      if (hasSemantic(param, hlsl::DXIL::SemanticKind::PrimitiveID))
+        primitiveIdVar = tempVar;
     }
   }
 
@@ -4914,11 +5029,24 @@ bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
   const uint32_t retVal =
       theBuilder.createFunctionCall(retType, entryFuncId, params);
 
-  // Create and write stage output variables for return value
-  if (!declIdMapper.createStageOutputVar(decl, retVal))
-    return false;
+  // Create and write stage output variables for return value. Special case for
+  // Hull shaders since they operate differently in 2 ways:
+  // 1- Their return value is in fact an array and each invocation should write
+  // to the proper offset in the array.
+  // 2- The patch constant function must be called *once* after all invocations
+  // of the main entry point function is done.
+  if (shaderModel.IsHS()) {
+    if (!processHullEntryPointOutputAndPatchConstFunc(
+            decl, retType, retVal, numOutputControlPoints,
+            outputControlPointIdVal, primitiveIdVar, hullMainInputPatchParam))
+      return false;
+  } else {
+    if (!declIdMapper.createStageOutputVar(decl, retVal, /*isPC*/ false))
+      return false;
+  }
 
-  // Create and write stage output variables for parameters marked as out/inout
+  // Create and write stage output variables for parameters marked as
+  // out/inout
   for (uint32_t i = 0; i < decl->getNumParams(); ++i) {
     const auto *param = decl->getParamDecl(i);
     if (param->getAttr<HLSLOutAttr>() || param->getAttr<HLSLInOutAttr>()) {
@@ -4926,7 +5054,8 @@ bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
       const uint32_t typeId = typeTranslator.translateType(param->getType());
       const uint32_t loadedParam = theBuilder.createLoad(typeId, params[i]);
 
-      if (!declIdMapper.createStageOutputVar(param, loadedParam))
+      if (!declIdMapper.createStageOutputVar(param, loadedParam,
+                                             /*isPC*/ false))
         return false;
     }
   }
@@ -4934,6 +5063,133 @@ bool SPIRVEmitter::emitEntryFunctionWrapper(const FunctionDecl *decl,
   theBuilder.createReturn();
   theBuilder.endFunction();
 
+  // For Hull shaders, there is no explicit call to the PCF in the HLSL source.
+  // We should invoke a translation of the PCF manually.
+  if (shaderModel.IsHS())
+    doDecl(patchConstFunc);
+
+  return true;
+}
+
+bool SPIRVEmitter::processHullEntryPointOutputAndPatchConstFunc(
+    const FunctionDecl *hullMainFuncDecl, uint32_t retType, uint32_t retVal,
+    uint32_t numOutputControlPoints, uint32_t outputControlPointId,
+    uint32_t primitiveId, uint32_t hullMainInputPatch) {
+
+  // This method may only be called for Hull shaders.
+  assert(shaderModel.IsHS());
+  uint32_t hullMainOutputPatch = 0;
+
+  // For Hull shaders, the real output is an array of size
+  // numOutputControlPoints. The results of the main should be written to the
+  // correct offset in the array (based on InvocationID).
+  if (!numOutputControlPoints) {
+    emitError("number of output control points cannot be zero",
+              hullMainFuncDecl->getLocation());
+    return false;
+  }
+  // TODO: We should be able to handle cases where the SV_OutputControlPointID
+  // is not provided.
+  if (!outputControlPointId) {
+    emitError(
+        "SV_OutputControlPointID semantic must be provided in the hull shader",
+        hullMainFuncDecl->getLocation());
+    return false;
+  }
+  if (!patchConstFunc) {
+    emitError("patch constant function not defined in hull shader",
+              hullMainFuncDecl->getLocation());
+    return false;
+  }
+
+  // Let's call the return value of the Hull entry point function
+  // "hllEntryPointOutput". The type of hullEntryPointOutput should be an
+  // array of size numOutputControlPoints.
+  const uint32_t hullEntryPointOutputType = theBuilder.getArrayType(
+      retType, theBuilder.getConstantUint32(numOutputControlPoints));
+  const auto loc = hullMainFuncDecl->getAttr<VKLocationAttr>();
+  const auto hullOutputVar = declIdMapper.createStageVarWithoutSemantics(
+      /*isInput*/ false, hullEntryPointOutputType, "hullEntryPointOutput", loc);
+  if (!hullOutputVar)
+    return false;
+
+  // Write the results into the correct Output array offset.
+  const auto location = theBuilder.createAccessChain(
+      theBuilder.getPointerType(retType, spv::StorageClass::Output),
+      hullOutputVar, {outputControlPointId});
+  theBuilder.createStore(location, retVal);
+
+  // If the patch constant function (PCF) takes the result of the Hull main
+  // entry point, create a temporary function-scope variable and write the
+  // results to it, so it can be passed to the PCF.
+  if (patchConstFuncTakesHullOutputPatch(patchConstFunc)) {
+    hullMainOutputPatch = theBuilder.addFnVar(hullEntryPointOutputType,
+                                              "temp.var.hullEntryPointOutput");
+    const auto tempLocation = theBuilder.createAccessChain(
+        theBuilder.getPointerType(retType, spv::StorageClass::Function),
+        hullMainOutputPatch, {outputControlPointId});
+    theBuilder.createStore(tempLocation, retVal);
+  }
+
+  // Now create a barrier before calling the Patch Constant Function (PCF).
+  // Flags are:
+  // Execution Barrier scope = Workgroup (2)
+  // Memory Barrier scope = Device (1)
+  // Memory Semantics Barrier scope = None (0)
+  theBuilder.createControlBarrier(theBuilder.getConstantUint32(2),
+                                  theBuilder.getConstantUint32(1),
+                                  theBuilder.getConstantUint32(0));
+
+  // The PCF should be called only once. Therefore, we check the invocationID,
+  // and we only allow ID 0 to call the PCF.
+  const uint32_t condition = theBuilder.createBinaryOp(
+      spv::Op::OpIEqual, theBuilder.getBoolType(), outputControlPointId,
+      theBuilder.getConstantUint32(0));
+  const uint32_t thenBB = theBuilder.createBasicBlock("if.true");
+  const uint32_t mergeBB = theBuilder.createBasicBlock("if.merge");
+  theBuilder.createConditionalBranch(condition, thenBB, mergeBB, mergeBB);
+  theBuilder.addSuccessor(thenBB);
+  theBuilder.addSuccessor(mergeBB);
+  theBuilder.setMergeTarget(mergeBB);
+
+  theBuilder.setInsertPoint(thenBB);
+  // Call the PCF. Since the function is not explicitly called, we must first
+  // register an ID for it.
+  const uint32_t pcfId = declIdMapper.getOrRegisterFnResultId(patchConstFunc);
+  const uint32_t pcfRetType =
+      typeTranslator.translateType(patchConstFunc->getReturnType());
+  std::vector<uint32_t> pcfParams;
+  for (const auto *param : patchConstFunc->parameters()) {
+    // Note: According to the HLSL reference, the PCF takes an InputPatch of
+    // ControlPoints as well as the PatchID (PrimitiveID). This does not
+    // necessarily mean that they are present. There is also no requirement
+    // for the order of parameters passed to PCF.
+    if (TypeTranslator::isInputPatch(param->getType()))
+      pcfParams.push_back(hullMainInputPatch);
+    if (TypeTranslator::isOutputPatch(param->getType()))
+      pcfParams.push_back(hullMainOutputPatch);
+    if (hasSemantic(param, hlsl::DXIL::SemanticKind::PrimitiveID)) {
+      if (!primitiveId) {
+        const uint32_t typeId = typeTranslator.translateType(param->getType());
+        std::string tempVarName = "param.var." + param->getNameAsString();
+        const uint32_t tempVar = theBuilder.addFnVar(typeId, tempVarName);
+        uint32_t loadedValue = 0;
+        declIdMapper.createStageInputVar(param, &loadedValue, /*isPC*/ true);
+        theBuilder.createStore(tempVar, loadedValue);
+        primitiveId = tempVar;
+      }
+      pcfParams.push_back(primitiveId);
+    }
+  }
+  const uint32_t pcfResultId =
+      theBuilder.createFunctionCall(pcfRetType, pcfId, {pcfParams});
+  if (!declIdMapper.createStageOutputVar(patchConstFunc, pcfResultId,
+                                         /*isPC*/ true))
+    return false;
+
+  theBuilder.createBranch(mergeBB);
+  theBuilder.addSuccessor(mergeBB);
+  theBuilder.setInsertPoint(mergeBB);
   return true;
 }
 

+ 38 - 0
tools/clang/lib/SPIRV/SPIRVEmitter.h

@@ -370,6 +370,13 @@ private:
   /// shader model.
   void AddExecutionModeForEntryPoint(uint32_t entryPointId);
 
+  /// \brief Adds necessary execution modes for the hull shader based on the
+  /// HLSL attributes of the entry point function.
+  /// Also writes the number of output control points to
+  /// *numOutputControlPoints. Returns true on success, and false on failure.
+  bool processHullShaderAttributes(const FunctionDecl *entryFunction,
+                                   uint32_t *numOutputControlPoints);
+
   /// \brief Emits a wrapper function for the entry function and returns true
   /// on success.
   ///
@@ -384,6 +391,33 @@ private:
   bool emitEntryFunctionWrapper(const FunctionDecl *entryFunction,
                                 uint32_t entryFuncId);
 
+  /// \brief Performs the following operations for the Hull shader:
+  /// * Creates an output variable which is an Array containing results for all
+  /// control points.
+  ///
+  /// * If the Patch Constant Function (PCF) takes the Hull main entry function
+  /// results (OutputPatch), it creates a temporary function-scope variable that
+  /// is then passed to the PCF.
+  ///
+  /// * Adds a control barrier (OpControlBarrier) to ensure all invocations are
+  /// done before PCF is called.
+  ///
+  /// * Prepares the necessary parameters to pass to the PCF (Can be one or more
+  /// of InputPatch, OutputPatch, PrimitiveId).
+  ///
+  /// * The execution thread with ControlPointId (invocationID) of 0 calls the
+  /// PCF. e.g. if(id == 0) pcf();
+  ///
+  /// * Gathers the results of the PCF and assigns them to stage output
+  /// variables.
+  ///
+  /// The method panics if it is called for any shader kind other than Hull
+  /// shaders.
+  bool processHullEntryPointOutputAndPatchConstFunc(
+      const FunctionDecl *hullMainFuncDecl, uint32_t retType, uint32_t retVal,
+      uint32_t numOutputControlPoints, uint32_t outputControlPointId,
+      uint32_t primitiveId, uint32_t hullMainInputPatch);
+
 private:
   /// \brief Returns true iff *all* the case values in the given switch
   /// statement are integer literals. In such cases OpSwitch can be used to
@@ -647,6 +681,10 @@ private:
 
   /// Maps a given statement to the basic block that is associated with it.
   llvm::DenseMap<const Stmt *, uint32_t> stmtBasicBlock;
+
+  /// This is the Patch Constant Function. This function is not explicitly
+  /// called from the entry point function.
+  FunctionDecl *patchConstFunc;
 };
 
 void SPIRVEmitter::doDeclStmt(const DeclStmt *declStmt) {

+ 32 - 0
tools/clang/lib/SPIRV/TypeTranslator.cpp

@@ -303,6 +303,20 @@ bool TypeTranslator::isScalarType(QualType type, QualType *scalarType) {
   return isScalar;
 }
 
+bool TypeTranslator::isOutputPatch(QualType type) {
+  if (const auto *rt = type->getAs<RecordType>()) {
+    return rt->getDecl()->getName() == "OutputPatch";
+  }
+  return false;
+}
+
+bool TypeTranslator::isInputPatch(QualType type) {
+  if (const auto *rt = type->getAs<RecordType>()) {
+    return rt->getDecl()->getName() == "InputPatch";
+  }
+  return false;
+}
+
 bool TypeTranslator::isRWByteAddressBuffer(QualType type) {
   if (const auto *rt = type->getAs<RecordType>()) {
     return rt->getDecl()->getName() == "RWByteAddressBuffer";
@@ -681,6 +695,24 @@ uint32_t TypeTranslator::translateResourceType(QualType type, LayoutRule rule) {
         /*depth*/ 0, /*isArray*/ 0, /*ms*/ 0,
         /*sampled*/ name == "Buffer" ? 1 : 2, format);
   }
+
+  // InputPatch
+  if (name == "InputPatch") {
+    const auto elemType = hlsl::GetHLSLInputPatchElementType(type);
+    const auto elemCount = hlsl::GetHLSLInputPatchCount(type);
+    const uint32_t elemTypeId = translateType(elemType);
+    const uint32_t elemCountId = theBuilder.getConstantUint32(elemCount);
+    return theBuilder.getArrayType(elemTypeId, elemCountId);
+  }
+  // OutputPatch
+  if (name == "OutputPatch") {
+    const auto elemType = hlsl::GetHLSLOutputPatchElementType(type);
+    const auto elemCount = hlsl::GetHLSLOutputPatchCount(type);
+    const uint32_t elemTypeId = translateType(elemType);
+    const uint32_t elemCountId = theBuilder.getConstantUint32(elemCount);
+    return theBuilder.getArrayType(elemTypeId, elemCountId);
+  }
+
   return 0;
 }
 

+ 6 - 0
tools/clang/lib/SPIRV/TypeTranslator.h

@@ -85,6 +85,12 @@ public:
   /// \brief Returns true if the given type is an HLSL RWTexture type.
   static bool isRWTexture(QualType);
 
+  /// \brief Returns true if the given type is an HLSL OutputPatch type.
+  static bool isOutputPatch(QualType);
+
+  /// \brief Returns true if the given type is an HLSL InputPatch type.
+  static bool isInputPatch(QualType);
+
   /// \brief Returns true if the given type will be translated into a SPIR-V
   /// scalar type. This includes normal scalar types, vectors of size 1, and
   /// 1x1 matrices. If scalarType is not nullptr, writes the scalar type to

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.domain.isoline.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+// CHECK: OpExecutionMode %SubDToBezierHS Isoline
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.domain.quad.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+// CHECK: OpExecutionMode %SubDToBezierHS Quads
+
+[domain("quad")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.domain.tri.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+// CHECK: OpExecutionMode %SubDToBezierHS Triangle
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 16 - 0
tools/clang/test/CodeGenSPIRV/attribute.outputcontrolpoints.hlsl

@@ -0,0 +1,16 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+[outputtopology("triangle_cw")]
+// CHECK: OpExecutionMode %SubDToBezierHS OutputVertices 16
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 16 - 0
tools/clang/test/CodeGenSPIRV/attribute.outputtopology.point.hlsl

@@ -0,0 +1,16 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+// CHECK: OpExecutionMode %SubDToBezierHS PointMode
+[outputtopology("point")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 16 - 0
tools/clang/test/CodeGenSPIRV/attribute.outputtopology.triangle-ccw.hlsl

@@ -0,0 +1,16 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+// CHECK: OpExecutionMode %SubDToBezierHS VertexOrderCcw
+[outputtopology("triangle_ccw")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 16 - 0
tools/clang/test/CodeGenSPIRV/attribute.outputtopology.triangle-cw.hlsl

@@ -0,0 +1,16 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+// CHECK: OpExecutionMode %SubDToBezierHS VertexOrderCw
+[outputtopology("triangle_cw")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.partitioning.fractional-even.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+
+[domain("tri")]
+// CHECK: OpExecutionMode %SubDToBezierHS SpacingFractionalEven
+[partitioning("fractional_even")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.partitioning.fractional-odd.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+
+[domain("tri")]
+// CHECK: OpExecutionMode %SubDToBezierHS SpacingFractionalOdd
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 17 - 0
tools/clang/test/CodeGenSPIRV/attribute.partitioning.integer.hlsl

@@ -0,0 +1,17 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#include "bezier_common_hull.hlsl"
+
+
+[domain("tri")]
+// CHECK: OpExecutionMode %SubDToBezierHS SpacingEqual
+[partitioning("integer")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 250 - 0
tools/clang/test/CodeGenSPIRV/bezier.hull.hlsl2spv

@@ -0,0 +1,250 @@
+// Run: %dxc -T hs_6_0 -E SubDToBezierHS
+
+#define MAX_POINTS 16
+
+// Input control point
+struct VS_CONTROL_POINT_OUTPUT
+{
+  float3 vPosition : WORLDPOS;
+  float2 vUV       : TEXCOORD0;
+  float3 vTangent  : TANGENT;
+};
+
+// Output control point
+struct BEZIER_CONTROL_POINT
+{
+  float3 vPosition	: BEZIERPOS;
+};
+
+// Output patch constant data.
+struct HS_CONSTANT_DATA_OUTPUT
+{
+  float Edges[4]        : SV_TessFactor;
+  float Inside[2]       : SV_InsideTessFactor;
+
+  float3 vTangent[4]    : TANGENT;
+  float2 vUV[4]         : TEXCOORD;
+  float3 vTanUCorner[4] : TANUCORNER;
+  float3 vTanVCorner[4] : TANVCORNER;
+  float4 vCWts          : TANWEIGHTS;
+};
+
+// Patch Constant Function
+HS_CONSTANT_DATA_OUTPUT SubDToBezierConstantsHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint PatchID : SV_PrimitiveID) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  
+  return Output;
+}
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+[outputtopology("triangle_ccw")]
+[outputcontrolpoints(MAX_POINTS)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT SubDToBezierHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint cpid : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}
+
+// CHECK-WHOLE-SPIR-V:
+// OpCapability Tessellation
+// OpMemoryModel Logical GLSL450
+// OpEntryPoint TessellationControl %SubDToBezierHS "SubDToBezierHS" %in_var_hullEntryPointInput %gl_InvocationID %gl_PrimitiveID %out_var_hullEntryPointOutput %gl_TessLevelOuter %gl_TessLevelInner %out_var_TANGENT %out_var_TEXCOORD %out_var_TANUCORNER %out_var_TANVCORNER %out_var_TANWEIGHTS
+// OpExecutionMode %SubDToBezierHS Triangles
+// OpExecutionMode %SubDToBezierHS SpacingFractionalOdd
+// OpExecutionMode %SubDToBezierHS VertexOrderCcw
+// OpExecutionMode %SubDToBezierHS OutputVertices 16
+// OpName %if_true "if.true"
+// OpName %if_merge "if.merge"
+// OpName %bb_entry "bb.entry"
+// OpName %bb_entry_0 "bb.entry"
+// OpName %src_SubDToBezierHS "src.SubDToBezierHS"
+// OpName %SubDToBezierHS "SubDToBezierHS"
+// OpName %VS_CONTROL_POINT_OUTPUT "VS_CONTROL_POINT_OUTPUT"
+// OpMemberName %VS_CONTROL_POINT_OUTPUT 0 "vPosition"
+// OpMemberName %VS_CONTROL_POINT_OUTPUT 1 "vUV"
+// OpMemberName %VS_CONTROL_POINT_OUTPUT 2 "vTangent"
+// OpName %param_var_ip "param.var.ip"
+// OpName %in_var_hullEntryPointInput "in.var.hullEntryPointInput"
+// OpName %param_var_cpid "param.var.cpid"
+// OpName %param_var_PatchID "param.var.PatchID"
+// OpName %BEZIER_CONTROL_POINT "BEZIER_CONTROL_POINT"
+// OpMemberName %BEZIER_CONTROL_POINT 0 "vPosition"
+// OpName %out_var_hullEntryPointOutput "out.var.hullEntryPointOutput"
+// OpName %SubDToBezierConstantsHS "SubDToBezierConstantsHS"
+// OpName %HS_CONSTANT_DATA_OUTPUT "HS_CONSTANT_DATA_OUTPUT"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 0 "Edges"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 1 "Inside"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 2 "vTangent"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 3 "vUV"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 4 "vTanUCorner"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 5 "vTanVCorner"
+// OpMemberName %HS_CONSTANT_DATA_OUTPUT 6 "vCWts"
+// OpName %out_var_TANGENT "out.var.TANGENT"
+// OpName %out_var_TEXCOORD "out.var.TEXCOORD"
+// OpName %out_var_TANUCORNER "out.var.TANUCORNER"
+// OpName %out_var_TANVCORNER "out.var.TANVCORNER"
+// OpName %out_var_TANWEIGHTS "out.var.TANWEIGHTS"
+// OpName %ip "ip"
+// OpName %PatchID "PatchID"
+// OpName %Output "Output"
+// OpName %ip_0 "ip"
+// OpName %cpid "cpid"
+// OpName %PatchID_0 "PatchID"
+// OpName %vsOutput "vsOutput"
+// OpName %result "result"
+// OpDecorate %gl_InvocationID BuiltIn InvocationId
+// OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+// OpDecorate %gl_TessLevelOuter BuiltIn TessLevelOuter
+// OpDecorate %gl_TessLevelInner BuiltIn TessLevelInner
+// OpDecorate %in_var_hullEntryPointInput Location 0
+// OpDecorate %out_var_hullEntryPointOutput Location 0
+// OpDecorate %out_var_TANGENT Location 1
+// OpDecorate %out_var_TEXCOORD Location 2
+// OpDecorate %out_var_TANUCORNER Location 3
+// OpDecorate %out_var_TANVCORNER Location 4
+// OpDecorate %out_var_TANWEIGHTS Location 5
+// %uint = OpTypeInt 32 0
+// %int = OpTypeInt 32 1
+// %void = OpTypeVoid
+// %3 = OpTypeFunction %void
+// %float = OpTypeFloat 32
+// %v3float = OpTypeVector %float 3
+// %v2float = OpTypeVector %float 2
+// %VS_CONTROL_POINT_OUTPUT = OpTypeStruct %v3float %v2float %v3float
+// %uint_16 = OpConstant %uint 16
+// %_arr_VS_CONTROL_POINT_OUTPUT_uint_16 = OpTypeArray %VS_CONTROL_POINT_OUTPUT %uint_16
+// %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 = OpTypePointer Function %_arr_VS_CONTROL_POINT_OUTPUT_uint_16
+// %_ptr_Input__arr_VS_CONTROL_POINT_OUTPUT_uint_16 = OpTypePointer Input %_arr_VS_CONTROL_POINT_OUTPUT_uint_16
+// %_ptr_Function_uint = OpTypePointer Function %uint
+// %_ptr_Input_uint = OpTypePointer Input %uint
+// %BEZIER_CONTROL_POINT = OpTypeStruct %v3float
+// %_arr_BEZIER_CONTROL_POINT_uint_16 = OpTypeArray %BEZIER_CONTROL_POINT %uint_16
+// %_ptr_Output__arr_BEZIER_CONTROL_POINT_uint_16 = OpTypePointer Output %_arr_BEZIER_CONTROL_POINT_uint_16
+// %_ptr_Output_BEZIER_CONTROL_POINT = OpTypePointer Output %BEZIER_CONTROL_POINT
+// %bool = OpTypeBool
+// %uint_4 = OpConstant %uint 4
+// %_arr_float_uint_4 = OpTypeArray %float %uint_4
+// %uint_2 = OpConstant %uint 2
+// %_arr_float_uint_2 = OpTypeArray %float %uint_2
+// %_arr_v3float_uint_4 = OpTypeArray %v3float %uint_4
+// %_arr_v2float_uint_4 = OpTypeArray %v2float %uint_4
+// %v4float = OpTypeVector %float 4
+// %HS_CONSTANT_DATA_OUTPUT = OpTypeStruct %_arr_float_uint_4 %_arr_float_uint_2 %_arr_v3float_uint_4 %_arr_v2float_uint_4 %_arr_v3float_uint_4 %_arr_v3float_uint_4 %v4float
+// %_ptr_Output__arr_float_uint_4 = OpTypePointer Output %_arr_float_uint_4
+// %_ptr_Output__arr_float_uint_2 = OpTypePointer Output %_arr_float_uint_2
+// %_ptr_Output__arr_v3float_uint_4 = OpTypePointer Output %_arr_v3float_uint_4
+// %_ptr_Output__arr_v2float_uint_4 = OpTypePointer Output %_arr_v2float_uint_4
+// %_ptr_Output_v4float = OpTypePointer Output %v4float
+// %68 = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 %_ptr_Function_uint
+// %_ptr_Function_HS_CONSTANT_DATA_OUTPUT = OpTypePointer Function %HS_CONSTANT_DATA_OUTPUT
+// %_ptr_Function_float = OpTypePointer Function %float
+// %94 = OpTypeFunction %BEZIER_CONTROL_POINT %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 %_ptr_Function_uint %_ptr_Function_uint
+// %_ptr_Function_VS_CONTROL_POINT_OUTPUT = OpTypePointer Function %VS_CONTROL_POINT_OUTPUT
+// %_ptr_Function_BEZIER_CONTROL_POINT = OpTypePointer Function %BEZIER_CONTROL_POINT
+// %_ptr_Function_v3float = OpTypePointer Function %v3float
+// %uint_0 = OpConstant %uint 0
+// %uint_1 = OpConstant %uint 1
+// %float_1 = OpConstant %float 1
+// %int_0 = OpConstant %int 0
+// %float_2 = OpConstant %float 2
+// %int_1 = OpConstant %int 1
+// %float_3 = OpConstant %float 3
+// %int_2 = OpConstant %int 2
+// %float_4 = OpConstant %float 4
+// %int_3 = OpConstant %int 3
+// %float_5 = OpConstant %float 5
+// %float_6 = OpConstant %float 6
+// %in_var_hullEntryPointInput = OpVariable %_ptr_Input__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Input
+// %gl_InvocationID = OpVariable %_ptr_Input_uint Input
+// %gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
+// %out_var_hullEntryPointOutput = OpVariable %_ptr_Output__arr_BEZIER_CONTROL_POINT_uint_16 Output
+// %gl_TessLevelOuter = OpVariable %_ptr_Output__arr_float_uint_4 Output
+// %gl_TessLevelInner = OpVariable %_ptr_Output__arr_float_uint_2 Output
+// %out_var_TANGENT = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// %out_var_TEXCOORD = OpVariable %_ptr_Output__arr_v2float_uint_4 Output
+// %out_var_TANUCORNER = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// %out_var_TANVCORNER = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// %out_var_TANWEIGHTS = OpVariable %_ptr_Output_v4float Output
+// %SubDToBezierHS = OpFunction %void None %3
+// %5 = OpLabel
+// %param_var_ip = OpVariable %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Function
+// %param_var_cpid = OpVariable %_ptr_Function_uint Function
+// %param_var_PatchID = OpVariable %_ptr_Function_uint Function
+// %17 = OpLoad %_arr_VS_CONTROL_POINT_OUTPUT_uint_16 %in_var_hullEntryPointInput
+// OpStore %param_var_ip %17
+// %22 = OpLoad %uint %gl_InvocationID
+// OpStore %param_var_cpid %22
+// %25 = OpLoad %uint %gl_PrimitiveID
+// OpStore %param_var_PatchID %25
+// %27 = OpFunctionCall %BEZIER_CONTROL_POINT %src_SubDToBezierHS %param_var_ip %param_var_cpid %param_var_PatchID
+// %32 = OpAccessChain %_ptr_Output_BEZIER_CONTROL_POINT %out_var_hullEntryPointOutput %22
+// OpStore %32 %27
+// OpControlBarrier %uint_2 %uint_1 %uint_0
+// %37 = OpIEqual %bool %22 %uint_0
+// OpSelectionMerge %if_merge None
+// OpBranchConditional %37 %if_true %if_merge
+// %if_true = OpLabel
+// %48 = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %SubDToBezierConstantsHS %param_var_ip %param_var_PatchID
+// %49 = OpCompositeExtract %_arr_float_uint_4 %48 0
+// OpStore %gl_TessLevelOuter %49
+// %52 = OpCompositeExtract %_arr_float_uint_2 %48 1
+// OpStore %gl_TessLevelInner %52
+// %55 = OpCompositeExtract %_arr_v3float_uint_4 %48 2
+// OpStore %out_var_TANGENT %55
+// %58 = OpCompositeExtract %_arr_v2float_uint_4 %48 3
+// OpStore %out_var_TEXCOORD %58
+// %61 = OpCompositeExtract %_arr_v3float_uint_4 %48 4
+// OpStore %out_var_TANUCORNER %61
+// %63 = OpCompositeExtract %_arr_v3float_uint_4 %48 5
+// OpStore %out_var_TANVCORNER %63
+// %65 = OpCompositeExtract %v4float %48 6
+// OpStore %out_var_TANWEIGHTS %65
+// OpBranch %if_merge
+// %if_merge = OpLabel
+// OpReturn
+// OpFunctionEnd
+// %SubDToBezierConstantsHS = OpFunction %HS_CONSTANT_DATA_OUTPUT None %68
+// %ip = OpFunctionParameter %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16
+// %PatchID = OpFunctionParameter %_ptr_Function_uint
+// %bb_entry = OpLabel
+// %Output = OpVariable %_ptr_Function_HS_CONSTANT_DATA_OUTPUT Function
+// %78 = OpAccessChain %_ptr_Function_float %Output %int_0 %int_0
+// OpStore %78 %float_1
+// %81 = OpAccessChain %_ptr_Function_float %Output %int_0 %int_1
+// OpStore %81 %float_2
+// %84 = OpAccessChain %_ptr_Function_float %Output %int_0 %int_2
+// OpStore %84 %float_3
+// %87 = OpAccessChain %_ptr_Function_float %Output %int_0 %int_3
+// OpStore %87 %float_4
+// %89 = OpAccessChain %_ptr_Function_float %Output %int_1 %int_0
+// OpStore %89 %float_5
+// %91 = OpAccessChain %_ptr_Function_float %Output %int_1 %int_1
+// OpStore %91 %float_6
+// %92 = OpLoad %HS_CONSTANT_DATA_OUTPUT %Output
+// OpReturnValue %92
+// OpFunctionEnd
+// %src_SubDToBezierHS = OpFunction %BEZIER_CONTROL_POINT None %94
+// %ip_0 = OpFunctionParameter %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16
+// %cpid = OpFunctionParameter %_ptr_Function_uint
+// %PatchID_0 = OpFunctionParameter %_ptr_Function_uint
+// %bb_entry_0 = OpLabel
+// %vsOutput = OpVariable %_ptr_Function_VS_CONTROL_POINT_OUTPUT Function
+// %result = OpVariable %_ptr_Function_BEZIER_CONTROL_POINT Function
+// %104 = OpAccessChain %_ptr_Function_v3float %vsOutput %int_0
+// %105 = OpLoad %v3float %104
+// %106 = OpAccessChain %_ptr_Function_v3float %result %int_0
+// OpStore %106 %105
+// %107 = OpLoad %BEZIER_CONTROL_POINT %result
+// OpReturnValue %107
+// OpFunctionEnd

+ 43 - 0
tools/clang/test/CodeGenSPIRV/bezier_common_hull.hlsl

@@ -0,0 +1,43 @@
+#define MAX_POINTS 16
+
+// Input control point
+struct VS_CONTROL_POINT_OUTPUT
+{
+  float3 vPosition : WORLDPOS;
+  float2 vUV       : TEXCOORD0;
+  float3 vTangent  : TANGENT;
+};
+
+// Output control point
+struct BEZIER_CONTROL_POINT
+{
+  float3 vPosition	: BEZIERPOS;
+};
+
+// Output patch constant data.
+struct HS_CONSTANT_DATA_OUTPUT
+{
+  float Edges[4]        : SV_TessFactor;
+  float Inside[2]       : SV_InsideTessFactor;
+
+  float3 vTangent[4]    : TANGENT;
+  float2 vUV[4]         : TEXCOORD;
+  float3 vTanUCorner[4] : TANUCORNER;
+  float3 vTanVCorner[4] : TANVCORNER;
+  float4 vCWts          : TANWEIGHTS;
+};
+
+// Patch Constant Function
+HS_CONSTANT_DATA_OUTPUT SubDToBezierConstantsHS(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint PatchID : SV_PrimitiveID) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  
+  return Output;
+}

+ 70 - 0
tools/clang/test/CodeGenSPIRV/hull.output-vars.hlsl

@@ -0,0 +1,70 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// This test ensures:
+// 1- Result of the main Hull shader entry point is written to output variable.
+// 2- TessLevelOuter and TessLevelInner builtins are written to.
+// 3- All other outputs of the PCF are written to output variables.
+
+// CHECK: OpEntryPoint TessellationControl %main "main" %in_var_hullEntryPointInput %gl_InvocationID %gl_PrimitiveID %out_var_hullEntryPointOutput %gl_TessLevelOuter %gl_TessLevelInner %out_var_TANGENT %out_var_TEXCOORD %out_var_TANUCORNER %out_var_TANVCORNER %out_var_TANWEIGHTS
+
+// CHECK: OpDecorate %gl_InvocationID BuiltIn InvocationId
+// CHECK: OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+// CHECK: OpDecorate %gl_TessLevelOuter BuiltIn TessLevelOuter
+// CHECK: OpDecorate %gl_TessLevelInner BuiltIn TessLevelInner
+// CHECK: OpDecorate %in_var_hullEntryPointInput Location 0
+// CHECK: OpDecorate %out_var_hullEntryPointOutput Location 0
+// CHECK: OpDecorate %out_var_TANGENT Location 1
+// CHECK: OpDecorate %out_var_TEXCOORD Location 2
+// CHECK: OpDecorate %out_var_TANUCORNER Location 3
+// CHECK: OpDecorate %out_var_TANVCORNER Location 4
+// CHECK: OpDecorate %out_var_TANWEIGHTS Location 5
+
+// CHECK:   %in_var_hullEntryPointInput = OpVariable %_ptr_Input__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Input
+// CHECK:              %gl_InvocationID = OpVariable %_ptr_Input_uint Input
+// CHECK:               %gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
+// CHECK: %out_var_hullEntryPointOutput = OpVariable %_ptr_Output__arr_BEZIER_CONTROL_POINT_uint_16 Output
+// CHECK:            %gl_TessLevelOuter = OpVariable %_ptr_Output__arr_float_uint_4 Output
+// CHECK:            %gl_TessLevelInner = OpVariable %_ptr_Output__arr_float_uint_2 Output
+// CHECK:              %out_var_TANGENT = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// CHECK:             %out_var_TEXCOORD = OpVariable %_ptr_Output__arr_v2float_uint_4 Output
+// CHECK:           %out_var_TANUCORNER = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// CHECK:           %out_var_TANVCORNER = OpVariable %_ptr_Output__arr_v3float_uint_4 Output
+// CHECK:           %out_var_TANWEIGHTS = OpVariable %_ptr_Output_v4float Output
+
+// CHECK:      %param_var_ip = OpVariable %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Function
+// CHECK:       %param_var_i = OpVariable %_ptr_Function_uint Function
+// CHECK: %param_var_PatchID = OpVariable %_ptr_Function_uint Function
+
+// CHECK: [[mainResult:%\d+]] = OpFunctionCall %BEZIER_CONTROL_POINT %src_main %param_var_ip %param_var_i %param_var_PatchID
+// CHECK: [[mainLoc:%\d+]] = OpAccessChain %_ptr_Output_BEZIER_CONTROL_POINT %out_var_hullEntryPointOutput {{%\d+}}
+// CHECK: OpStore [[mainLoc]] [[mainResult]]
+
+// CHECK:      [[PCFResult:%\d+]] = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %SubDToBezierConstantsHS %param_var_ip %param_var_PatchID
+// CHECK-NEXT:       [[tso:%\d+]] = OpCompositeExtract %_arr_float_uint_4 [[PCFResult]] 0
+// CHECK-NEXT:                      OpStore %gl_TessLevelOuter [[tso]]
+// CHECK-NEXT:       [[tsi:%\d+]] = OpCompositeExtract %_arr_float_uint_2 [[PCFResult]] 1
+// CHECK-NEXT:                      OpStore %gl_TessLevelInner [[tsi]]
+// CHECK-NEXT:   [[tangent:%\d+]] = OpCompositeExtract %_arr_v3float_uint_4 [[PCFResult]] 2
+// CHECK-NEXT:                      OpStore %out_var_TANGENT [[tangent]]
+// CHECK-NEXT:  [[texcoord:%\d+]] = OpCompositeExtract %_arr_v2float_uint_4 [[PCFResult]] 3
+// CHECK-NEXT:                      OpStore %out_var_TEXCOORD [[texcoord]]
+// CHECK-NEXT:      [[tanu:%\d+]] = OpCompositeExtract %_arr_v3float_uint_4 [[PCFResult]] 4
+// CHECK-NEXT:                      OpStore %out_var_TANUCORNER [[tanu]]
+// CHECK-NEXT:      [[tanv:%\d+]] = OpCompositeExtract %_arr_v3float_uint_4 [[PCFResult]] 5
+// CHECK-NEXT:                      OpStore %out_var_TANVCORNER [[tanv]]
+// CHECK-NEXT:      [[tanw:%\d+]] = OpCompositeExtract %v4float [[PCFResult]] 6
+// CHECK-NEXT:                      OpStore %out_var_TANWEIGHTS [[tanw]]
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 45 - 0
tools/clang/test/CodeGenSPIRV/hull.pcf.input-patch.hlsl

@@ -0,0 +1,45 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// Test: PCF takes the input control points (InputPatch)
+
+// CHECK: OpEntryPoint TessellationControl %main "main" %in_var_hullEntryPointInput {{%\w+}}
+
+// CHECK:              [[fType:%\d+]] = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16
+// CHECK: %in_var_hullEntryPointInput = OpVariable %_ptr_Input__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Input
+
+// CHECK:                       %main = OpFunction %void None {{%\d+}}
+// CHECK:               %param_var_ip = OpVariable %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16 Function
+// CHECK:            [[hull_in:%\d+]] = OpLoad %_arr_VS_CONTROL_POINT_OUTPUT_uint_16 %in_var_hullEntryPointInput
+// CHECK:                               OpStore %param_var_ip [[hull_in]]
+
+// CHECK:                    {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %PCF %param_var_ip
+
+// CHECK:                        %PCF = OpFunction %HS_CONSTANT_DATA_OUTPUT None [[fType]]
+// CHECK-NEXT:                    %ip = OpFunctionParameter %_ptr_Function__arr_VS_CONTROL_POINT_OUTPUT_uint_16
+
+
+HS_CONSTANT_DATA_OUTPUT PCF(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  return Output;
+}
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("PCF")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 53 - 0
tools/clang/test/CodeGenSPIRV/hull.pcf.output-patch.hlsl

@@ -0,0 +1,53 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// Test: PCF takes the output (OutputPatch) of the main entry point function.
+
+
+// CHECK: OpEntryPoint TessellationControl %main "main" {{%\w+}} {{%\w+}} {{%\w+}} %out_var_hullEntryPointOutput {{%\w+}}
+
+// CHECK:               %_arr_BEZIER_CONTROL_POINT_uint_16 = OpTypeArray %BEZIER_CONTROL_POINT %uint_16
+// CHECK:   %_ptr_Output__arr_BEZIER_CONTROL_POINT_uint_16 = OpTypePointer Output %_arr_BEZIER_CONTROL_POINT_uint_16
+// CHECK: %_ptr_Function__arr_BEZIER_CONTROL_POINT_uint_16 = OpTypePointer Function %_arr_BEZIER_CONTROL_POINT_uint_16
+// CHECK:                                   [[fType:%\d+]] = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT %_ptr_Function__arr_BEZIER_CONTROL_POINT_uint_16
+// CHECK:                    %out_var_hullEntryPointOutput = OpVariable %_ptr_Output__arr_BEZIER_CONTROL_POINT_uint_16 Output
+
+// CHECK:                             %main = OpFunction %void None {{%\d+}}
+// CHECK:    %temp_var_hullEntryPointOutput = OpVariable %_ptr_Function__arr_BEZIER_CONTROL_POINT_uint_16 Function
+
+// CHECK:              [[id:%\d+]] = OpLoad %uint %gl_InvocationID
+// CHECK:      [[mainResult:%\d+]] = OpFunctionCall %BEZIER_CONTROL_POINT %src_main %param_var_ip %param_var_i %param_var_PatchID
+// CHECK-NEXT:  [[outputLoc:%\d+]] = OpAccessChain %_ptr_Output_BEZIER_CONTROL_POINT %out_var_hullEntryPointOutput [[id]]
+// CHECK-NEXT:                       OpStore [[outputLoc]] [[mainResult]]
+// CHECK-NEXT:    [[tempLoc:%\d+]] = OpAccessChain %_ptr_Function_BEZIER_CONTROL_POINT %temp_var_hullEntryPointOutput [[id]]
+// CHECK-NEXT:                       OpStore [[tempLoc]] [[mainResult]]
+
+// CHECK:                 {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %PCF %temp_var_hullEntryPointOutput
+
+// CHECK:      %PCF = OpFunction %HS_CONSTANT_DATA_OUTPUT None [[fType]]
+// CHECK-NEXT:  %op = OpFunctionParameter %_ptr_Function__arr_BEZIER_CONTROL_POINT_uint_16
+
+HS_CONSTANT_DATA_OUTPUT PCF(OutputPatch<BEZIER_CONTROL_POINT, MAX_POINTS> op) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  return Output;
+}
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("PCF")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 49 - 0
tools/clang/test/CodeGenSPIRV/hull.pcf.primitive-id-2.hlsl

@@ -0,0 +1,49 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// Test: PCF takes the PrimitiveID
+// Note that in this test, the main entry point *DOES NOT* take the PrimitiveID as input.
+
+
+// CHECK: OpEntryPoint TessellationControl %main "main" {{%\w+}} {{%\w+}} {{%\w+}} %gl_PrimitiveID {{%\w+}}
+
+// CHECK: OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+
+// CHECK:     [[fType:%\d+]] = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT %_ptr_Function_uint
+// CHECK:    %gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
+
+// CHECK:              %main = OpFunction %void None {{%\d+}}
+// CHECK: %param_var_PatchID = OpVariable %_ptr_Function_uint Function
+
+// CHECK: [[pid:%\d+]] = OpLoad %uint %gl_PrimitiveID
+// CHECK:                OpStore %param_var_PatchID [[pid]]
+
+// CHECK: {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %PCF %param_var_PatchID
+
+// CHECK:     %PCF = OpFunction %HS_CONSTANT_DATA_OUTPUT None [[fType]]
+// CHECK: %PatchID = OpFunctionParameter %_ptr_Function_uint
+
+HS_CONSTANT_DATA_OUTPUT PCF(uint PatchID : SV_PrimitiveID) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  return Output;
+}
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("PCF")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 49 - 0
tools/clang/test/CodeGenSPIRV/hull.pcf.primitive-id.hlsl

@@ -0,0 +1,49 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// Test: PCF takes the PrimitiveID
+// Note that in this test, the main entry point has also taken the PrimitiveID as input.
+
+
+// CHECK: OpEntryPoint TessellationControl %main "main" {{%\w+}} {{%\w+}} %gl_PrimitiveID {{%\w+}}
+
+// CHECK: OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+
+// CHECK:     [[fType:%\d+]] = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT %_ptr_Function_uint
+// CHECK:    %gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
+
+// CHECK:              %main = OpFunction %void None {{%\d+}}
+// CHECK: %param_var_PatchID = OpVariable %_ptr_Function_uint Function
+
+// CHECK: [[pid:%\d+]] = OpLoad %uint %gl_PrimitiveID
+// CHECK:                OpStore %param_var_PatchID [[pid]]
+
+// CHECK: {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %PCF %param_var_PatchID
+
+// CHECK:     %PCF = OpFunction %HS_CONSTANT_DATA_OUTPUT None [[fType]]
+// CHECK: %PatchID = OpFunctionParameter %_ptr_Function_uint
+
+HS_CONSTANT_DATA_OUTPUT PCF(uint PatchID : SV_PrimitiveID) {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  return Output;
+}
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("PCF")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 33 - 0
tools/clang/test/CodeGenSPIRV/hull.pcf.void.hlsl

@@ -0,0 +1,33 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// CHECK: [[fType:%\d+]] = OpTypeFunction %HS_CONSTANT_DATA_OUTPUT
+// CHECK:          %main = OpFunction %void None {{%\d+}}
+// CHECK:       {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %PCF
+// CHECK:           %PCF = OpFunction %HS_CONSTANT_DATA_OUTPUT None [[fType]]
+
+// PCF does not take any args
+HS_CONSTANT_DATA_OUTPUT PCF() {
+  HS_CONSTANT_DATA_OUTPUT Output;
+  // Must initialize Edges and Inside; otherwise HLSL validation will fail.
+  Output.Edges[0]  = 1.0;
+  Output.Edges[1]  = 2.0;
+  Output.Edges[2]  = 3.0;
+  Output.Edges[3]  = 4.0;
+  Output.Inside[0] = 5.0;
+  Output.Inside[1] = 6.0;
+  return Output;
+}
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("PCF")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

+ 41 - 0
tools/clang/test/CodeGenSPIRV/hull.structure.hlsl

@@ -0,0 +1,41 @@
+// Run: %dxc -T hs_6_0 -E main
+
+#include "bezier_common_hull.hlsl"
+
+// This test ensures:
+// 1- The hull shader entry point function is invoked.
+// 2- Control barrier is placed before PCF is called.
+// 3- PCF is called by invocationID of 0.
+
+// CHECK:         %main = OpFunction %void None {{%\d+}}
+// CHECK:   [[id:%\d+]] = OpLoad %uint %gl_InvocationID
+
+// CHECK:  [[hullOutput:%\d+]] = OpFunctionCall %BEZIER_CONTROL_POINT %src_main %param_var_ip %param_var_i %param_var_PatchID
+
+// CHECK: [[outLocation:%\d+]] = OpAccessChain %_ptr_Output_BEZIER_CONTROL_POINT %out_var_hullEntryPointOutput [[id]]
+// CHECK:                        OpStore [[outLocation]] [[hullOutput]]
+
+// CHECK:                 OpControlBarrier %uint_2 %uint_1 %uint_0
+
+// CHECK: [[cond:%\d+]] = OpIEqual %bool [[id]] %uint_0
+// CHECK:                 OpSelectionMerge %if_merge None
+// CHECK:                 OpBranchConditional [[cond]] %if_true %if_merge
+// CHECK:      %if_true = OpLabel
+// CHECK:      {{%\d+}} = OpFunctionCall %HS_CONSTANT_DATA_OUTPUT %SubDToBezierConstantsHS %param_var_ip %param_var_PatchID
+// CHECK:                 OpBranch %if_merge
+// CHECK:     %if_merge = OpLabel
+// CHECK:                 OpReturn
+// CHECK:                 OpFunctionEnd
+
+
+[domain("isoline")]
+[partitioning("fractional_odd")]
+[outputtopology("line")]
+[outputcontrolpoints(16)]
+[patchconstantfunc("SubDToBezierConstantsHS")]
+BEZIER_CONTROL_POINT main(InputPatch<VS_CONTROL_POINT_OUTPUT, MAX_POINTS> ip, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID) {
+  VS_CONTROL_POINT_OUTPUT vsOutput;
+  BEZIER_CONTROL_POINT result;
+  result.vPosition = vsOutput.vPosition;
+  return result;
+}

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

@@ -36,6 +36,10 @@ TEST_F(WholeFileTest, ConstantPixelShader) {
   runWholeFileTest("constant-ps.hlsl2spv", /*generateHeader*/ true);
 }
 
+TEST_F(WholeFileTest, BezierHullShader) {
+  runWholeFileTest("bezier.hull.hlsl2spv");
+}
+
 // === Partial output tests ===
 
 // For types
@@ -586,6 +590,36 @@ TEST_F(FileTest, AttributeNumThreads) {
 TEST_F(FileTest, AttributeMissingNumThreads) {
   runFileTest("attribute.numthreads.missing.hlsl");
 }
+TEST_F(FileTest, AttributeDomainTri) {
+  runFileTest("attribute.domain.tri.hlsl");
+}
+TEST_F(FileTest, AttributeDomainQuad) {
+  runFileTest("attribute.domain.quad.hlsl");
+}
+TEST_F(FileTest, AttributeDomainIsoline) {
+  runFileTest("attribute.domain.isoline.hlsl");
+}
+TEST_F(FileTest, AttributePartitioningInteger) {
+  runFileTest("attribute.partitioning.integer.hlsl");
+}
+TEST_F(FileTest, AttributePartitioningFractionalEven) {
+  runFileTest("attribute.partitioning.fractional-even.hlsl");
+}
+TEST_F(FileTest, AttributePartitioningFractionalOdd) {
+  runFileTest("attribute.partitioning.fractional-odd.hlsl");
+}
+TEST_F(FileTest, AttributeOutputTopologyPoint) {
+  runFileTest("attribute.outputtopology.point.hlsl");
+}
+TEST_F(FileTest, AttributeOutputTopologyTriangleCw) {
+  runFileTest("attribute.outputtopology.triangle-cw.hlsl");
+}
+TEST_F(FileTest, AttributeOutputTopologyTriangleCcw) {
+  runFileTest("attribute.outputtopology.triangle-ccw.hlsl");
+}
+TEST_F(FileTest, AttributeOutputControlPoints) {
+  runFileTest("attribute.outputcontrolpoints.hlsl");
+}
 
 // Vulkan/SPIR-V specific
 TEST_F(FileTest, SpirvStorageClass) { runFileTest("spirv.storage-class.hlsl"); }
@@ -655,4 +689,25 @@ TEST_F(FileTest, VulkanLayoutConsumeSBufferStd430) {
   runFileTest("vk.layout.csbuffer.std430.hlsl");
 }
 
+// For different Patch Constant Functions (for Hull shaders)
+TEST_F(FileTest, HullShaderPCFVoid) {
+  runFileTest("hull.pcf.void.hlsl");
+}
+TEST_F(FileTest, HullShaderPCFTakesInputPatch) {
+  runFileTest("hull.pcf.input-patch.hlsl");
+}
+TEST_F(FileTest, HullShaderPCFTakesOutputPatch) {
+  runFileTest("hull.pcf.output-patch.hlsl");
+}
+TEST_F(FileTest, HullShaderPCFTakesPrimitiveId) {
+  runFileTest("hull.pcf.primitive-id.hlsl");
+}
+TEST_F(FileTest, HullShaderPCFTakesPrimitiveIdButMainDoesnt) {
+  runFileTest("hull.pcf.primitive-id-2.hlsl");
+}
+// For Hull Shader Output variables
+TEST_F(FileTest, HullShaderOutputVars) { runFileTest("hull.output-vars.hlsl"); }
+// For the structure of Hull Shaders
+TEST_F(FileTest, HullShaderStructure) { runFileTest("hull.structure.hlsl"); }
+
 } // namespace