diff --git a/libs/vkd3d-shader/d3dbc.c b/libs/vkd3d-shader/d3dbc.c
index bda9bc72f5673e6d7e679a2b1081e34a47f53140..f28ca418898a18fd9caf05ec5ad8085530e6d49f 100644
--- a/libs/vkd3d-shader/d3dbc.c
+++ b/libs/vkd3d-shader/d3dbc.c
@@ -1566,6 +1566,7 @@ D3DXPARAMETER_CLASS hlsl_sm1_class(const struct hlsl_type *type)
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             break;
     }
@@ -1673,6 +1674,7 @@ D3DXPARAMETER_TYPE hlsl_sm1_base_type(const struct hlsl_type *type)
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             break;
     }
diff --git a/libs/vkd3d-shader/fx.c b/libs/vkd3d-shader/fx.c
index cb42551ee8b64e7bc74da6c1aefc4f782dbc4a0b..31156f059ebe3867f57d2b534aab56dd6ad32df7 100644
--- a/libs/vkd3d-shader/fx.c
+++ b/libs/vkd3d-shader/fx.c
@@ -761,6 +761,7 @@ static uint32_t write_fx_4_type(const struct hlsl_type *type, struct fx_write_co
         case HLSL_CLASS_PASS:
         case HLSL_CLASS_TECHNIQUE:
         case HLSL_CLASS_CONSTANT_BUFFER:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
         case HLSL_CLASS_STREAM_OUTPUT:
             vkd3d_unreachable();
@@ -1291,6 +1292,7 @@ static bool is_type_supported_fx_2(struct hlsl_ctx *ctx, const struct hlsl_type
         case HLSL_CLASS_HULL_SHADER:
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
+        case HLSL_CLASS_PATCH:
             return false;
 
         case HLSL_CLASS_EFFECT_GROUP:
diff --git a/libs/vkd3d-shader/hlsl.c b/libs/vkd3d-shader/hlsl.c
index 3be9ba9979b98dc1c8d066922d1ab8506ff64fb8..692e254a142db8fbef54642bb56495deed696d94 100644
--- a/libs/vkd3d-shader/hlsl.c
+++ b/libs/vkd3d-shader/hlsl.c
@@ -288,6 +288,7 @@ bool hlsl_type_is_shader(const struct hlsl_type *type)
         case HLSL_CLASS_CONSTANT_BUFFER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_VOID:
         case HLSL_CLASS_NULL:
             return false;
@@ -436,6 +437,7 @@ static void hlsl_type_calculate_reg_size(struct hlsl_ctx *ctx, struct hlsl_type
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             break;
     }
@@ -521,6 +523,7 @@ static bool type_is_single_component(const struct hlsl_type *type)
         case HLSL_CLASS_STRUCT:
         case HLSL_CLASS_ARRAY:
         case HLSL_CLASS_CONSTANT_BUFFER:
+        case HLSL_CLASS_PATCH:
             return false;
 
         case HLSL_CLASS_EFFECT_GROUP:
@@ -682,6 +685,7 @@ unsigned int hlsl_type_get_component_offset(struct hlsl_ctx *ctx, struct hlsl_ty
             case HLSL_CLASS_VOID:
             case HLSL_CLASS_SCALAR:
             case HLSL_CLASS_CONSTANT_BUFFER:
+            case HLSL_CLASS_PATCH:
             case HLSL_CLASS_NULL:
             case HLSL_CLASS_STREAM_OUTPUT:
                 vkd3d_unreachable();
@@ -876,6 +880,9 @@ struct hlsl_type *hlsl_get_element_type_from_path_index(struct hlsl_ctx *ctx, co
             return type->e.record.fields[c->value.u[0].u].type;
         }
 
+        case HLSL_CLASS_PATCH:
+            return type->e.patch.type;
+
         default:
             vkd3d_unreachable();
     }
@@ -987,6 +994,25 @@ struct hlsl_type *hlsl_new_cb_type(struct hlsl_ctx *ctx, struct hlsl_type *forma
     return type;
 }
 
+struct hlsl_type *hlsl_new_patch_type(struct hlsl_ctx *ctx, enum hlsl_patch_kind kind,
+        struct hlsl_type *format, unsigned int control_points_count)
+{
+    struct hlsl_type *type;
+
+    if (!(type = hlsl_alloc(ctx, sizeof(*type))))
+        return NULL;
+    type->modifiers = HLSL_MODIFIER_CONST;
+    type->class = HLSL_CLASS_PATCH;
+    type->dimx = format->dimx;
+    type->dimy = format->dimy;
+    type->e.patch.kind = kind;
+    type->e.patch.type = format;
+    type->e.patch.control_points_count = control_points_count;
+    hlsl_type_calculate_reg_size(ctx, type);
+    list_add_tail(&ctx->types, &type->entry);
+    return type;
+}
+
 static const char * get_case_insensitive_typename(const char *name)
 {
     static const char *const names[] =
@@ -1099,6 +1125,7 @@ unsigned int hlsl_type_component_count(const struct hlsl_type *type)
         case HLSL_CLASS_HULL_SHADER:
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             return 1;
 
@@ -1172,6 +1199,10 @@ bool hlsl_types_are_equal(const struct hlsl_type *t1, const struct hlsl_type *t2
             return t1->e.array.elements_count == t2->e.array.elements_count
                     && hlsl_types_are_equal(t1->e.array.type, t2->e.array.type);
 
+        case HLSL_CLASS_PATCH:
+            return t1->e.patch.control_points_count == t2->e.patch.control_points_count
+                    && hlsl_types_are_equal(t1->e.patch.type, t2->e.patch.type);
+
         case HLSL_CLASS_TECHNIQUE:
             return t1->e.version == t2->e.version;
 
@@ -1298,6 +1329,17 @@ struct hlsl_type *hlsl_type_clone(struct hlsl_ctx *ctx, struct hlsl_type *old,
             type->e.version = old->e.version;
             break;
 
+        case HLSL_CLASS_PATCH:
+            if (!(type->e.patch.type = hlsl_type_clone(ctx, old->e.patch.type, default_majority, modifiers)))
+            {
+                vkd3d_free((void *)type->name);
+                vkd3d_free(type);
+                return NULL;
+            }
+            type->e.patch.control_points_count = old->e.patch.control_points_count;
+            type->e.patch.kind = old->e.patch.kind;
+            break;
+
         default:
             break;
     }
@@ -2876,6 +2918,18 @@ struct vkd3d_string_buffer *hlsl_type_to_string(struct hlsl_ctx *ctx, const stru
             }
             return string;
 
+        case HLSL_CLASS_PATCH:
+            if (type->e.patch.kind == HLSL_PATCH_KIND_INPUT)
+                vkd3d_string_buffer_printf(string, "InputPatch");
+            else
+                vkd3d_string_buffer_printf(string, "OutputPatch");
+            if ((inner_string = hlsl_type_to_string(ctx, type->e.patch.type)))
+            {
+                vkd3d_string_buffer_printf(string, "<%s, %u>", inner_string->buffer, type->e.patch.control_points_count);
+                hlsl_release_string_buffer(ctx, inner_string);
+            }
+            return string;
+
         case HLSL_CLASS_DEPTH_STENCIL_STATE:
         case HLSL_CLASS_DEPTH_STENCIL_VIEW:
         case HLSL_CLASS_EFFECT_GROUP:
@@ -4474,6 +4528,7 @@ static bool hlsl_ctx_init(struct hlsl_ctx *ctx, const struct vkd3d_shader_compil
     hlsl_block_add_instr(&ctx->static_initializers, ctx->error_instr);
 
     ctx->domain = VKD3D_TESSELLATOR_DOMAIN_INVALID;
+    ctx->input_control_point_count = 1;
     ctx->output_control_point_count = UINT_MAX;
     ctx->output_primitive = 0;
     ctx->partitioning = 0;
diff --git a/libs/vkd3d-shader/hlsl.h b/libs/vkd3d-shader/hlsl.h
index 4dbc04139519303ad8111aee661091f9d5be471e..1ff00deceea4712a84a74caeb48e003e1b764b77 100644
--- a/libs/vkd3d-shader/hlsl.h
+++ b/libs/vkd3d-shader/hlsl.h
@@ -105,6 +105,7 @@ enum hlsl_type_class
     HLSL_CLASS_CONSTANT_BUFFER,
     HLSL_CLASS_BLEND_STATE,
     HLSL_CLASS_STREAM_OUTPUT,
+    HLSL_CLASS_PATCH,
     HLSL_CLASS_VOID,
     HLSL_CLASS_NULL,
     HLSL_CLASS_ERROR,
@@ -149,6 +150,12 @@ enum hlsl_so_object_type
     HLSL_STREAM_OUTPUT_TRIANGLE_STREAM,
 };
 
+enum hlsl_patch_kind
+{
+    HLSL_PATCH_KIND_INPUT,
+    HLSL_PATCH_KIND_OUTPUT,
+};
+
 enum hlsl_regset
 {
     HLSL_REGSET_SAMPLERS,
@@ -225,6 +232,13 @@ struct hlsl_type
             /* The type is a rasteriser-ordered view. */
             bool rasteriser_ordered;
         } resource;
+        /* Additional information if type is HLSL_CLASS_PATCH. */
+        struct
+        {
+            enum hlsl_patch_kind kind;
+            struct hlsl_type *type;
+            unsigned int control_points_count;
+        } patch;
         /* Additional field to distinguish object types. Currently used only for technique types. */
         unsigned int version;
         /* Additional information if type is HLSL_CLASS_STREAM_OUTPUT. */
@@ -1133,6 +1147,9 @@ struct hlsl_ctx
     enum vkd3d_shader_tessellator_partitioning partitioning;
     struct hlsl_ir_function_decl *patch_constant_func;
 
+    unsigned int input_control_point_count;
+    struct hlsl_type *input_control_point_type;
+
     /* In some cases we generate opcodes by parsing an HLSL function and then
      * invoking it. If not NULL, this field is the name of the function that we
      * are currently parsing, "mangled" with an internal prefix to avoid
@@ -1590,6 +1607,8 @@ struct hlsl_type *hlsl_new_texture_type(struct hlsl_ctx *ctx, enum hlsl_sampler_
 struct hlsl_type *hlsl_new_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
         struct hlsl_type *format, bool rasteriser_ordered);
 struct hlsl_type *hlsl_new_cb_type(struct hlsl_ctx *ctx, struct hlsl_type *format);
+struct hlsl_type *hlsl_new_patch_type(struct hlsl_ctx *ctx,  enum hlsl_patch_kind kind, struct hlsl_type *format,
+        unsigned int control_points_count);
 struct hlsl_ir_node *hlsl_new_uint_constant(struct hlsl_ctx *ctx, unsigned int n,
         const struct vkd3d_shader_location *loc);
 struct hlsl_ir_node *hlsl_new_null_constant(struct hlsl_ctx *ctx, const struct vkd3d_shader_location *loc);
diff --git a/libs/vkd3d-shader/hlsl.l b/libs/vkd3d-shader/hlsl.l
index 31fb30521e97393823b4d565af621e6f804d0e5c..605a9abaa930e4987b4c211647eea3d4942218ff 100644
--- a/libs/vkd3d-shader/hlsl.l
+++ b/libs/vkd3d-shader/hlsl.l
@@ -104,6 +104,7 @@ if                      {return KW_IF;                  }
 in                      {return KW_IN;                  }
 inline                  {return KW_INLINE;              }
 inout                   {return KW_INOUT;               }
+InputPatch              {return KW_INPUTPATCH;          }
 LineStream              {return KW_LINESTREAM;          }
 linear                  {return KW_LINEAR;              }
 matrix                  {return KW_MATRIX;              }
@@ -112,6 +113,7 @@ nointerpolation         {return KW_NOINTERPOLATION;     }
 noperspective           {return KW_NOPERSPECTIVE;       }
 NULL                    {return KW_NULL;                }
 out                     {return KW_OUT;                 }
+OutputPatch             {return KW_OUTPUTPATCH;         }
 packoffset              {return KW_PACKOFFSET;          }
 pass                    {return KW_PASS;                }
 PixelShader             {return KW_PIXELSHADER;         }
diff --git a/libs/vkd3d-shader/hlsl.y b/libs/vkd3d-shader/hlsl.y
index 03a2f38e4e9aa897f2450fbf658de1f785a73baa..ec744a7cafe5eceeaa47c10d202567397e1c6490 100644
--- a/libs/vkd3d-shader/hlsl.y
+++ b/libs/vkd3d-shader/hlsl.y
@@ -1072,6 +1072,8 @@ static bool add_array_access(struct hlsl_ctx *ctx, struct hlsl_block *block, str
     {
         if (expr_type->class == HLSL_CLASS_SCALAR)
             hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_INDEX, "Scalar expressions cannot be array-indexed.");
+        else if (expr_type->class == HLSL_CLASS_PATCH)
+            hlsl_fixme(ctx, loc, "Patch array access.");
         else
             hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_INVALID_INDEX, "Expression cannot be array-indexed.");
         return false;
@@ -2745,6 +2747,12 @@ static void declare_var(struct hlsl_ctx *ctx, struct parse_variable_def *v)
                     "packoffset() is only allowed inside constant buffer declarations.");
     }
 
+    if (type->class == HLSL_CLASS_PATCH && !v->initializer.args_count)
+    {
+        hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_MISSING_INITIALIZER,
+            "Patch variable \"%s\" is missing an initializer.", var->name);
+    }
+
     if (ctx->cur_scope == ctx->globals)
     {
         local = false;
@@ -6554,6 +6562,7 @@ static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
     enum hlsl_buffer_type buffer_type;
     enum hlsl_sampler_dim sampler_dim;
     enum hlsl_so_object_type so_type;
+    enum hlsl_patch_kind patch_kind;
     struct hlsl_attribute *attr;
     struct parse_attribute_list attr_list;
     struct hlsl_ir_switch_case *switch_case;
@@ -6596,6 +6605,7 @@ static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
 %token KW_IN
 %token KW_INLINE
 %token KW_INOUT
+%token KW_INPUTPATCH
 %token KW_LINEAR
 %token KW_LINESTREAM
 %token KW_MATRIX
@@ -6604,6 +6614,7 @@ static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
 %token KW_NOPERSPECTIVE
 %token KW_NULL
 %token KW_OUT
+%token KW_OUTPUTPATCH
 %token KW_PACKOFFSET
 %token KW_PASS
 %token KW_PIXELSHADER
@@ -6785,6 +6796,7 @@ static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
 %type <reg_reservation> packoffset_reservation
 
 %type <sampler_dim> texture_type texture_ms_type uav_type rov_type
+%type <patch_kind> patch_type
 
 %type <semantic> semantic
 
@@ -7834,6 +7846,16 @@ resource_format:
                 YYABORT;
         }
 
+patch_type:
+      KW_INPUTPATCH
+        {
+            $$ = HLSL_PATCH_KIND_INPUT;
+        }
+    | KW_OUTPUTPATCH
+        {
+            $$ = HLSL_PATCH_KIND_OUTPUT;
+        }
+
 type_no_void:
       KW_VECTOR '<' type ',' C_INTEGER '>'
         {
@@ -7972,6 +7994,17 @@ type_no_void:
         {
             $$ = hlsl_new_stream_output_type(ctx, $1, $3);
         }
+    | patch_type '<' type ',' C_INTEGER '>'
+        {
+            if ($5 < 1)
+            {
+                hlsl_error(ctx, &@5, VKD3D_SHADER_ERROR_HLSL_INVALID_SIZE,
+                        "Control point size %d is not positive.", $5);
+                YYABORT;
+            }
+
+            $$ = hlsl_new_patch_type(ctx, $1, $3, $5);
+        }
     | KW_RWBYTEADDRESSBUFFER
         {
             $$ = hlsl_new_uav_type(ctx, HLSL_SAMPLER_DIM_RAW_BUFFER, hlsl_get_scalar_type(ctx, HLSL_TYPE_UINT), false);
diff --git a/libs/vkd3d-shader/hlsl_codegen.c b/libs/vkd3d-shader/hlsl_codegen.c
index 1fbf670f0323f33d4a37909c2956336171908bb0..fbfa61d9a04ca6a685793d4c0e9a9faece1c7c04 100644
--- a/libs/vkd3d-shader/hlsl_codegen.c
+++ b/libs/vkd3d-shader/hlsl_codegen.c
@@ -160,9 +160,9 @@ static bool replace_deref_path_with_offset(struct hlsl_ctx *ctx, struct hlsl_der
 
     type = hlsl_deref_get_type(ctx, deref);
 
-    /* Instructions that directly refer to structs or arrays (instead of single-register components)
+    /* Instructions that directly refer to structs, arrays or patches (instead of single-register components)
      * are removed later by dce. So it is not a problem to just cleanup their derefs. */
-    if (type->class == HLSL_CLASS_STRUCT || type->class == HLSL_CLASS_ARRAY)
+    if (type->class == HLSL_CLASS_STRUCT || type->class == HLSL_CLASS_ARRAY || type->class == HLSL_CLASS_PATCH)
     {
         hlsl_cleanup_deref(deref);
         return true;
@@ -640,6 +640,30 @@ static void append_output_var_copy(struct hlsl_ctx *ctx, struct hlsl_ir_function
     append_output_copy_recurse(ctx, func, load, var->storage_modifiers, &var->semantic, var->semantic.index, false);
 }
 
+static void add_patch_vars(struct hlsl_ctx *ctx, struct hlsl_ir_function_decl *func, struct hlsl_ir_var *var)
+{
+    struct hlsl_type *type = var->data_type;
+
+    VKD3D_ASSERT(type->e.patch.control_points_count > 0);
+
+    if (ctx->input_control_point_count)
+    {
+        if (type->e.patch.control_points_count != ctx->input_control_point_count
+                || !hlsl_types_are_equal(type->e.patch.type, ctx->input_control_point_type))
+        {
+            hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Patch type mismatch.");
+            return;
+        }
+    }
+    else
+    {
+        ctx->input_control_point_count = type->e.patch.control_points_count;
+        ctx->input_control_point_type = type->e.patch.type;
+    }
+
+    /* TODO: Generate semantic variables from patch definition. */
+}
+
 bool hlsl_transform_ir(struct hlsl_ctx *ctx, bool (*func)(struct hlsl_ctx *ctx, struct hlsl_ir_node *, void *),
         struct hlsl_block *block, void *context)
 {
@@ -1680,6 +1704,7 @@ static bool copy_propagation_transform_load(struct hlsl_ctx *ctx,
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             break;
 
@@ -9409,8 +9434,10 @@ static void process_entry_function(struct hlsl_ctx *ctx,
     const struct hlsl_profile_info *profile = ctx->profile;
     struct hlsl_block static_initializers, global_uniforms;
     struct hlsl_block *const body = &entry_func->body;
+    bool is_patch_constant_func = entry_func == ctx->patch_constant_func;
     struct recursive_call_ctx recursive_call_ctx;
     struct hlsl_ir_var *var;
+    bool found_inputpatch, found_outputpatch;
     unsigned int i;
 
     if (!hlsl_clone_block(ctx, &static_initializers, &ctx->static_initializers))
@@ -9443,6 +9470,7 @@ static void process_entry_function(struct hlsl_ctx *ctx,
     lower_ir(ctx, lower_matrix_swizzles, body);
     lower_ir(ctx, lower_index_loads, body);
 
+    found_inputpatch = found_outputpatch = false;
     for (i = 0; i < entry_func->parameters.count; ++i)
     {
         var = entry_func->parameters.vars[i];
@@ -9453,12 +9481,65 @@ static void process_entry_function(struct hlsl_ctx *ctx,
         }
         else if ((var->storage_modifiers & HLSL_STORAGE_UNIFORM))
         {
-            if (ctx->profile->type == VKD3D_SHADER_TYPE_HULL && entry_func == ctx->patch_constant_func)
+            if (ctx->profile->type == VKD3D_SHADER_TYPE_HULL && is_patch_constant_func)
                 hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_MODIFIER,
                         "Patch constant function parameter \"%s\" cannot be uniform.", var->name);
             else
                 prepend_uniform_copy(ctx, body, var);
         }
+        else if (hlsl_get_multiarray_element_type(var->data_type)->class == HLSL_CLASS_PATCH)
+        {
+            if (var->data_type->e.patch.kind == HLSL_PATCH_KIND_INPUT)
+            {
+                if (found_inputpatch)
+                {
+                    hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_DUPLICATE_PATCH,
+                            "Duplicate InputPatch parameter.");
+                    continue;
+                }
+                found_inputpatch = true;
+
+                if (profile->type != VKD3D_SHADER_TYPE_HULL && profile->type != VKD3D_SHADER_TYPE_GEOMETRY)
+                {
+                    hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
+                            "InputPatch parameters are only supported in hull shaders and geometry shaders.");
+                    continue;
+                }
+
+                if (profile->type == VKD3D_SHADER_TYPE_GEOMETRY)
+                {
+                    hlsl_fixme(ctx, &var->loc, "InputPatch parameter in geometry shaders.");
+                    continue;
+                }
+
+                add_patch_vars(ctx, entry_func, var);
+            }
+            else /* HLSL_PATCH_KIND_OUTPUT */
+            {
+                if (found_outputpatch)
+                {
+                    hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_DUPLICATE_PATCH,
+                            "Duplicate OutputPatch parameter.");
+                    continue;
+                }
+                found_outputpatch = true;
+
+                if (!is_patch_constant_func && profile->type != VKD3D_SHADER_TYPE_DOMAIN)
+                {
+                    hlsl_error(ctx, &var->loc, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE,
+                            "OutputPatch parameters are only supported in patch constant functions and domain shaders.");
+                    continue;
+                }
+
+                if (is_patch_constant_func)
+                {
+                    hlsl_fixme(ctx, &var->loc, "OutputPatch parameter in patch constant functions.");
+                    continue;
+                }
+
+                add_patch_vars(ctx, entry_func, var);
+            }
+        }
         else
         {
             if (hlsl_get_multiarray_element_type(var->data_type)->class != HLSL_CLASS_STRUCT
diff --git a/libs/vkd3d-shader/tpf.c b/libs/vkd3d-shader/tpf.c
index e1ca5d6f0aca095b4ba4bc8d5edf6047427cd845..d32e65552103612194a7a40b60804b6c0a35436e 100644
--- a/libs/vkd3d-shader/tpf.c
+++ b/libs/vkd3d-shader/tpf.c
@@ -3333,6 +3333,7 @@ static D3D_SHADER_VARIABLE_CLASS sm4_class(const struct hlsl_type *type)
         case HLSL_CLASS_GEOMETRY_SHADER:
         case HLSL_CLASS_BLEND_STATE:
         case HLSL_CLASS_STREAM_OUTPUT:
+        case HLSL_CLASS_PATCH:
         case HLSL_CLASS_NULL:
             break;
     }
@@ -5772,7 +5773,7 @@ static void tpf_write_shdr(struct tpf_compiler *tpf, struct hlsl_ir_function_dec
     {
         tpf_write_hs_decls(tpf);
 
-        tpf_write_dcl_input_control_point_count(tpf, 1); /* TODO: Obtain from InputPatch */
+        tpf_write_dcl_input_control_point_count(tpf, ctx->input_control_point_count);
         tpf_write_dcl_output_control_point_count(tpf, ctx->output_control_point_count);
         tpf_write_dcl_tessellator_domain(tpf, ctx->domain);
         tpf_write_dcl_tessellator_partitioning(tpf, ctx->partitioning);
@@ -5780,7 +5781,7 @@ static void tpf_write_shdr(struct tpf_compiler *tpf, struct hlsl_ir_function_dec
     }
     else if (version->type == VKD3D_SHADER_TYPE_DOMAIN)
     {
-        tpf_write_dcl_input_control_point_count(tpf, 0); /* TODO: Obtain from OutputPatch */
+        tpf_write_dcl_input_control_point_count(tpf, ctx->input_control_point_count);
         tpf_write_dcl_tessellator_domain(tpf, ctx->domain);
     }
 
diff --git a/libs/vkd3d-shader/vkd3d_shader_private.h b/libs/vkd3d-shader/vkd3d_shader_private.h
index b9f2b25c84c93aefea65b31adfed636620af919d..b09cd30566e09bacf0b58201575c6e039cf63438 100644
--- a/libs/vkd3d-shader/vkd3d_shader_private.h
+++ b/libs/vkd3d-shader/vkd3d_shader_private.h
@@ -163,6 +163,7 @@ enum vkd3d_shader_error
     VKD3D_SHADER_ERROR_HLSL_INVALID_OUTPUT_PRIMITIVE    = 5037,
     VKD3D_SHADER_ERROR_HLSL_INVALID_PARTITIONING        = 5038,
     VKD3D_SHADER_ERROR_HLSL_MISPLACED_SAMPLER_STATE     = 5039,
+    VKD3D_SHADER_ERROR_HLSL_DUPLICATE_PATCH             = 5040,
 
     VKD3D_SHADER_WARNING_HLSL_IMPLICIT_TRUNCATION       = 5300,
     VKD3D_SHADER_WARNING_HLSL_DIVISION_BY_ZERO          = 5301,