diff --git a/Makefile.am b/Makefile.am
index f823cbc85b602554cebf11a78cd799035a7af034..2ad556c77524b9b2b1582617686ca1270ba2d59f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -202,6 +202,7 @@ vkd3d_shader_tests = \
 	tests/hlsl/swizzle-matrix.shader_test \
 	tests/hlsl/swizzles.shader_test \
 	tests/hlsl/ternary.shader_test \
+	tests/hlsl/tessellation.shader_test \
 	tests/hlsl/texture-load-offset.shader_test \
 	tests/hlsl/texture-load-typed.shader_test \
 	tests/hlsl/texture-load.shader_test \
diff --git a/tests/hlsl/tessellation.shader_test b/tests/hlsl/tessellation.shader_test
new file mode 100644
index 0000000000000000000000000000000000000000..d8b2634736e87a98abf9da1db90e3fc37b05ea16
--- /dev/null
+++ b/tests/hlsl/tessellation.shader_test
@@ -0,0 +1,98 @@
+[require]
+shader model >= 5.0
+
+[vertex shader]
+struct data
+{
+    float4 position : SV_Position;
+    float r : RED;
+    float g : GREEN;
+    float b : BLUE;
+};
+
+void main(uint id : SV_VertexID, out data output)
+{
+    float2 coords = float2((id << 1) & 2, id & 2);
+    output.position = float4(coords * float2(2, -2) + float2(-1, 1), 0, 1);
+    output.r = 0.0;
+    output.g = 1.0;
+    output.b = 0.0;
+}
+
+[hull shader todo]
+struct data
+{
+    float4 position : SV_Position;
+    float r : RED;
+    float g : GREEN;
+    float b : BLUE;
+};
+
+struct patch_constant_data
+{
+    float edges[3] : SV_TessFactor;
+    float inside : SV_InsideTessFactor;
+};
+
+void patch_constant(InputPatch<data, 3> input, out patch_constant_data output)
+{
+    output.edges[0] = output.edges[1] = output.edges[2] = 1.0f;
+    output.inside = 1.0f;
+}
+
+    [domain("tri")]
+    [outputcontrolpoints(3)]
+    [partitioning("integer")]
+    [outputtopology("triangle_cw")]
+    [patchconstantfunc("patch_constant")]
+data main(InputPatch<data, 3> input, uint i : SV_OutputControlPointID)
+{
+    return input[i];
+}
+
+[domain shader todo]
+struct data
+{
+    float4 position : SV_Position;
+    float r : RED;
+    float g : GREEN;
+    float b : BLUE;
+};
+
+struct patch_constant_data
+{
+    float edges[3] : SV_TessFactor;
+    float inside : SV_InsideTessFactor;
+};
+
+    [domain("tri")]
+void main(patch_constant_data input,
+        float3 tess_coord : SV_DomainLocation,
+        const OutputPatch<data, 3> patch,
+        out data output)
+{
+    output.position = tess_coord.x * patch[0].position
+            + tess_coord.y * patch[1].position
+            + tess_coord.z * patch[2].position;
+    output.r = tess_coord.x * patch[0].r + tess_coord.y * patch[1].r + tess_coord.z * patch[2].r;
+    output.g = tess_coord.x * patch[0].g + tess_coord.y * patch[1].g + tess_coord.z * patch[2].g;
+    output.b = tess_coord.x * patch[0].b + tess_coord.y * patch[1].b + tess_coord.z * patch[2].b;
+}
+
+[pixel shader]
+struct data
+{
+    float4 position : SV_Position;
+    float r : RED;
+    float g : GREEN;
+    float b : BLUE;
+};
+
+float4 main(data input) : sv_target
+{
+    return float4(input.r, input.g, input.b, 1.0);
+}
+
+[test]
+todo draw 3 control point patch list 3
+probe all rgba (0.0, 1.0, 0.0, 1.0)
diff --git a/tests/shader_runner.c b/tests/shader_runner.c
index f800ded323d133e0ab93f40a1ba822812d8bd2fc..359eafb95234a7d7dee231e5f47b3aec4a93a9d8 100644
--- a/tests/shader_runner.c
+++ b/tests/shader_runner.c
@@ -107,6 +107,10 @@ enum parse_state
     STATE_SHADER_VERTEX_TODO,
     STATE_SHADER_EFFECT,
     STATE_SHADER_EFFECT_TODO,
+    STATE_SHADER_HULL,
+    STATE_SHADER_HULL_TODO,
+    STATE_SHADER_DOMAIN,
+    STATE_SHADER_DOMAIN_TODO,
     STATE_TEST,
 };
 
@@ -818,6 +822,9 @@ static void parse_test_directive(struct shader_runner *runner, const char *line)
             "    return pos;\n"
             "}";
 
+        if (!runner->hs_source != !runner->ds_source)
+            fatal_error("Have a domain or hull shader but not both.\n");
+
         if (!shader_runner_get_resource(runner, RESOURCE_TYPE_RENDER_TARGET, 0))
         {
             memset(&params, 0, sizeof(params));
@@ -868,6 +875,9 @@ static void parse_test_directive(struct shader_runner *runner, const char *line)
         struct resource_params params;
         unsigned int vertex_count;
 
+        if (!runner->hs_source != !runner->ds_source)
+            fatal_error("Have a domain or hull shader but not both.\n");
+
         if (!shader_runner_get_resource(runner, RESOURCE_TYPE_RENDER_TARGET, 0))
         {
             memset(&params, 0, sizeof(params));
@@ -888,6 +898,14 @@ static void parse_test_directive(struct shader_runner *runner, const char *line)
             topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
         else if (match_string(line, "triangle strip", &line))
             topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+        else if (match_string(line, "1 control point patch list", &line))
+            topology = D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST;
+        else if (match_string(line, "2 control point patch list", &line))
+            topology = D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST;
+        else if (match_string(line, "3 control point patch list", &line))
+            topology = D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST;
+        else if (match_string(line, "4 control point patch list", &line))
+            topology = D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
         else
             fatal_error("Unknown primitive topology '%s'.\n", line);
 
@@ -1205,6 +1223,8 @@ const char *shader_type_string(enum shader_type type)
         [SHADER_TYPE_CS] = "cs",
         [SHADER_TYPE_PS] = "ps",
         [SHADER_TYPE_VS] = "vs",
+        [SHADER_TYPE_HS] = "hs",
+        [SHADER_TYPE_DS] = "ds",
         [SHADER_TYPE_FX] = "fx",
     };
     assert(type < ARRAY_SIZE(shader_types));
@@ -1266,6 +1286,8 @@ HRESULT dxc_compiler_compile_shader(void *dxc_compiler, enum shader_type type, u
         [SHADER_TYPE_CS] = L"cs_6_0",
         [SHADER_TYPE_PS] = L"ps_6_0",
         [SHADER_TYPE_VS] = L"vs_6_0",
+        [SHADER_TYPE_HS] = L"hs_6_0",
+        [SHADER_TYPE_DS] = L"ds_6_0",
     };
     const WCHAR *args[] =
     {
@@ -1393,6 +1415,10 @@ static enum parse_state get_parse_state_todo(enum parse_state state)
         return STATE_SHADER_PIXEL_TODO;
     else if (state == STATE_SHADER_VERTEX)
         return STATE_SHADER_VERTEX_TODO;
+    else if (state == STATE_SHADER_HULL)
+        return STATE_SHADER_HULL_TODO;
+    else if (state == STATE_SHADER_DOMAIN)
+        return STATE_SHADER_DOMAIN_TODO;
     else
         return STATE_SHADER_EFFECT_TODO;
 }
@@ -1613,6 +1639,36 @@ void run_shader_tests(struct shader_runner *runner, const struct shader_runner_c
                     shader_source_size = 0;
                     break;
 
+                case STATE_SHADER_HULL:
+                case STATE_SHADER_HULL_TODO:
+                    if (!skip_tests)
+                    {
+                        todo_if (state == STATE_SHADER_HULL_TODO)
+                        compile_shader(runner, dxc_compiler, shader_source, shader_source_len, SHADER_TYPE_HS,
+                                expect_hr);
+                    }
+                    free(runner->hs_source);
+                    runner->hs_source = shader_source;
+                    shader_source = NULL;
+                    shader_source_len = 0;
+                    shader_source_size = 0;
+                    break;
+
+                case STATE_SHADER_DOMAIN:
+                case STATE_SHADER_DOMAIN_TODO:
+                    if (!skip_tests)
+                    {
+                        todo_if (state == STATE_SHADER_DOMAIN_TODO)
+                        compile_shader(runner, dxc_compiler, shader_source, shader_source_len, SHADER_TYPE_DS,
+                                expect_hr);
+                    }
+                    free(runner->ds_source);
+                    runner->ds_source = shader_source;
+                    shader_source = NULL;
+                    shader_source_len = 0;
+                    shader_source_size = 0;
+                    break;
+
                 case STATE_PREPROC_INVALID:
                 {
                     ID3D10Blob *blob = NULL, *errors = NULL;
@@ -1795,6 +1851,18 @@ void run_shader_tests(struct shader_runner *runner, const struct shader_runner_c
                 expect_hr = S_OK;
                 state = read_shader_directive(runner, state, line_buffer, line, &expect_hr);
             }
+            else if (match_directive_substring(line, "[hull shader", &line))
+            {
+                state = STATE_SHADER_HULL;
+                expect_hr = S_OK;
+                state = read_shader_directive(runner, state, line_buffer, line, &expect_hr);
+            }
+            else if (match_directive_substring(line, "[domain shader", &line))
+            {
+                state = STATE_SHADER_DOMAIN;
+                expect_hr = S_OK;
+                state = read_shader_directive(runner, state, line_buffer, line, &expect_hr);
+            }
             else if (!strcmp(line, "[input layout]\n"))
             {
                 state = STATE_INPUT_LAYOUT;
@@ -1826,6 +1894,10 @@ void run_shader_tests(struct shader_runner *runner, const struct shader_runner_c
                 case STATE_SHADER_VERTEX_TODO:
                 case STATE_SHADER_EFFECT:
                 case STATE_SHADER_EFFECT_TODO:
+                case STATE_SHADER_HULL:
+                case STATE_SHADER_HULL_TODO:
+                case STATE_SHADER_DOMAIN:
+                case STATE_SHADER_DOMAIN_TODO:
                 {
                     size_t len = strlen(line);
 
diff --git a/tests/shader_runner.h b/tests/shader_runner.h
index 2f7e8fb25122fcd255adc1c8d94835d6eb63caee..dd92048f5fa6d2c02e05d6ef5dfd3e2139111cc6 100644
--- a/tests/shader_runner.h
+++ b/tests/shader_runner.h
@@ -45,6 +45,8 @@ enum shader_type
     SHADER_TYPE_CS,
     SHADER_TYPE_PS,
     SHADER_TYPE_VS,
+    SHADER_TYPE_HS,
+    SHADER_TYPE_DS,
     SHADER_TYPE_FX,
 };
 
@@ -157,6 +159,8 @@ struct shader_runner
     char *ps_source;
     char *cs_source;
     char *fx_source;
+    char *hs_source;
+    char *ds_source;
     enum shader_model minimum_shader_model;
     enum shader_model maximum_shader_model;
     bool require_float64;
diff --git a/tests/shader_runner_d3d11.c b/tests/shader_runner_d3d11.c
index b26d6a36b8d44e2809f8bf8627f5214594c87293..bc1c5c5f53e16345552055ff082b1e3110a961bc 100644
--- a/tests/shader_runner_d3d11.c
+++ b/tests/shader_runner_d3d11.c
@@ -589,24 +589,46 @@ static bool d3d11_runner_draw(struct shader_runner *r,
 {
     ID3D11UnorderedAccessView *uavs[D3D11_PS_CS_UAV_REGISTER_COUNT] = {0};
     ID3D11RenderTargetView *rtvs[D3D11_PS_CS_UAV_REGISTER_COUNT] = {0};
+    ID3D10Blob *vs_code, *ps_code, *hs_code = NULL, *ds_code = NULL;
     struct d3d11_shader_runner *runner = d3d11_shader_runner(r);
     ID3D11DeviceContext *context = runner->immediate_context;
     unsigned int min_uav_slot = ARRAY_SIZE(uavs);
     ID3D11Device *device = runner->device;
-    ID3D10Blob *vs_code, *ps_code;
     unsigned int rtv_count = 0;
     ID3D11Buffer *cb = NULL;
     ID3D11VertexShader *vs;
+    ID3D11DomainShader *ds;
     ID3D11PixelShader *ps;
+    ID3D11HullShader *hs;
+    bool succeeded;
     unsigned int i;
     HRESULT hr;
 
-    if (!(vs_code = compile_shader(runner, runner->r.vs_source, "vs")))
-        return false;
+    vs_code = compile_shader(runner, runner->r.vs_source, "vs");
+    ps_code = compile_shader(runner, runner->r.ps_source, "ps");
+    succeeded = vs_code && ps_code;
 
-    if (!(ps_code = compile_shader(runner, runner->r.ps_source, "ps")))
+    if (runner->r.hs_source)
+    {
+        hs_code = compile_shader(runner, runner->r.hs_source, "hs");
+        succeeded = succeeded && hs_code;
+    }
+    if (runner->r.ds_source)
     {
-        ID3D10Blob_Release(vs_code);
+        ds_code = compile_shader(runner, runner->r.ds_source, "ds");
+        succeeded = succeeded && ds_code;
+    }
+
+    if (!succeeded)
+    {
+        if (ps_code)
+            ID3D10Blob_Release(ps_code);
+        if (vs_code)
+            ID3D10Blob_Release(vs_code);
+        if (hs_code)
+            ID3D10Blob_Release(hs_code);
+        if (ds_code)
+            ID3D10Blob_Release(ds_code);
         return false;
     }
 
@@ -618,12 +640,35 @@ static bool d3d11_runner_draw(struct shader_runner *r,
             ID3D10Blob_GetBufferSize(ps_code), NULL, &ps);
     ok(hr == S_OK, "Failed to create pixel shader, hr %#lx.\n", hr);
 
+    if (hs_code)
+    {
+        hr = ID3D11Device_CreateHullShader(device, ID3D10Blob_GetBufferPointer(hs_code),
+                ID3D10Blob_GetBufferSize(hs_code), NULL, &hs);
+        ok(hr == S_OK, "Failed to create hull shader, hr %#lx.\n", hr);
+    }
+    if (ds_code)
+    {
+        hr = ID3D11Device_CreateDomainShader(device, ID3D10Blob_GetBufferPointer(ds_code),
+                ID3D10Blob_GetBufferSize(ds_code), NULL, &ds);
+        ok(hr == S_OK, "Failed to create domain shader, hr %#lx.\n", hr);
+    }
+
+    ID3D10Blob_Release(ps_code);
+    if (hs_code)
+        ID3D10Blob_Release(hs_code);
+    if (ds_code)
+        ID3D10Blob_Release(ds_code);
+
     if (runner->r.uniform_count)
     {
         cb = create_buffer(device, D3D11_BIND_CONSTANT_BUFFER,
                 runner->r.uniform_count * sizeof(*runner->r.uniforms), 0, runner->r.uniforms);
         ID3D11DeviceContext_VSSetConstantBuffers(context, 0, 1, &cb);
         ID3D11DeviceContext_PSSetConstantBuffers(context, 0, 1, &cb);
+        if (hs_code)
+            ID3D11DeviceContext_HSSetConstantBuffers(context, 0, 1, &cb);
+        if (ds_code)
+            ID3D11DeviceContext_DSSetConstantBuffers(context, 0, 1, &cb);
     }
 
     for (i = 0; i < runner->r.resource_count; ++i)
@@ -694,15 +739,25 @@ static bool d3d11_runner_draw(struct shader_runner *r,
         ID3D11InputLayout_Release(input_layout);
     }
 
+    ID3D10Blob_Release(vs_code);
+
     ID3D11DeviceContext_IASetPrimitiveTopology(context, primitive_topology);
     ID3D11DeviceContext_VSSetShader(context, vs, NULL, 0);
     ID3D11DeviceContext_PSSetShader(context, ps, NULL, 0);
+    if (hs_code)
+        ID3D11DeviceContext_HSSetShader(context, hs, NULL, 0);
+    if (ds_code)
+        ID3D11DeviceContext_DSSetShader(context, ds, NULL, 0);
     ID3D11DeviceContext_RSSetState(context, runner->rasterizer_state);
 
     ID3D11DeviceContext_Draw(context, vertex_count, 0);
 
     ID3D11PixelShader_Release(ps);
     ID3D11VertexShader_Release(vs);
+    if (hs_code)
+        ID3D11HullShader_Release(hs);
+    if (ds_code)
+        ID3D11DomainShader_Release(ds);
     if (cb)
         ID3D11Buffer_Release(cb);
 
diff --git a/tests/shader_runner_d3d12.c b/tests/shader_runner_d3d12.c
index 56844ae364e00002db183f22e857863b6357cab2..48792fa7a2c510347852d5d594289611e8994f36 100644
--- a/tests/shader_runner_d3d12.c
+++ b/tests/shader_runner_d3d12.c
@@ -434,29 +434,47 @@ static bool d3d12_runner_draw(struct shader_runner *r,
     struct test_context *test_context = &runner->test_context;
 
     D3D12_CPU_DESCRIPTOR_HANDLE rtvs[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT] = {0};
+    ID3D10Blob *vs_code, *ps_code, *hs_code = NULL, *ds_code = NULL;
     ID3D12GraphicsCommandList *command_list = test_context->list;
     D3D12_GRAPHICS_PIPELINE_STATE_DESC pso_desc = {0};
     ID3D12CommandQueue *queue = test_context->queue;
     D3D12_INPUT_ELEMENT_DESC *input_element_descs;
     ID3D12Device *device = test_context->device;
-    ID3D10Blob *vs_code, *ps_code;
     unsigned int uniform_index;
     unsigned int rtv_count = 0;
     ID3D12PipelineState *pso;
+    bool succeeded;
     HRESULT hr;
     size_t i;
 
     ps_code = compile_shader(runner, runner->r.ps_source, SHADER_TYPE_PS);
     vs_code = compile_shader(runner, runner->r.vs_source, SHADER_TYPE_VS);
+    succeeded = ps_code && vs_code;
+
+    if (runner->r.hs_source)
+    {
+        hs_code = compile_shader(runner, runner->r.hs_source, SHADER_TYPE_HS);
+        succeeded = succeeded && hs_code;
+    }
+    if (runner->r.ds_source)
+    {
+        ds_code = compile_shader(runner, runner->r.ds_source, SHADER_TYPE_DS);
+        succeeded = succeeded && ds_code;
+    }
+
     todo_if(runner->r.is_todo && runner->r.minimum_shader_model < SHADER_MODEL_6_0)
-    ok(ps_code && vs_code, "Failed to compile shaders.\n");
+    ok(succeeded, "Failed to compile shaders.\n");
 
-    if (!ps_code || !vs_code)
+    if (!succeeded)
     {
         if (ps_code)
             ID3D10Blob_Release(ps_code);
         if (vs_code)
             ID3D10Blob_Release(vs_code);
+        if (hs_code)
+            ID3D10Blob_Release(hs_code);
+        if (ds_code)
+            ID3D10Blob_Release(ds_code);
         return false;
     }
 
@@ -481,9 +499,20 @@ static bool d3d12_runner_draw(struct shader_runner *r,
     pso_desc.VS.BytecodeLength = ID3D10Blob_GetBufferSize(vs_code);
     pso_desc.PS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ps_code);
     pso_desc.PS.BytecodeLength = ID3D10Blob_GetBufferSize(ps_code);
+    if (hs_code)
+    {
+        pso_desc.HS.pShaderBytecode = ID3D10Blob_GetBufferPointer(hs_code);
+        pso_desc.HS.BytecodeLength = ID3D10Blob_GetBufferSize(hs_code);
+        pso_desc.DS.pShaderBytecode = ID3D10Blob_GetBufferPointer(ds_code);
+        pso_desc.DS.BytecodeLength = ID3D10Blob_GetBufferSize(ds_code);
+        pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
+    }
+    else
+    {
+        pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
+    }
     pso_desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
     pso_desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
-    pso_desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
     pso_desc.SampleDesc.Count = 1;
     pso_desc.SampleMask = ~(UINT)0;
     pso_desc.pRootSignature = test_context->root_signature;
@@ -510,6 +539,10 @@ static bool d3d12_runner_draw(struct shader_runner *r,
     todo_if(runner->r.is_todo) ok(hr == S_OK, "Failed to create state, hr %#x.\n", hr);
     ID3D10Blob_Release(vs_code);
     ID3D10Blob_Release(ps_code);
+    if (hs_code)
+        ID3D10Blob_Release(hs_code);
+    if (ds_code)
+        ID3D10Blob_Release(ds_code);
     free(input_element_descs);
 
     if (FAILED(hr))
diff --git a/tests/shader_runner_gl.c b/tests/shader_runner_gl.c
index 2045950e39ab4a77078bad9dcc41640e8fd90870..221f356483eed44ea64a304ef06c03a813ab9642 100644
--- a/tests/shader_runner_gl.c
+++ b/tests/shader_runner_gl.c
@@ -804,20 +804,40 @@ static GLenum get_compare_op_gl(D3D12_COMPARISON_FUNC op)
 
 static GLuint compile_graphics_shader_program(struct gl_runner *runner, ID3D10Blob **vs_blob)
 {
+    ID3D10Blob *fs_blob, *hs_blob = NULL, *ds_blob = NULL;
     struct vkd3d_shader_code vs_code, fs_code;
     GLuint program_id, vs_id, fs_id;
     const GLchar *source;
-    ID3D10Blob *fs_blob;
     GLint status, size;
+    bool succeeded;
 
     reset_combined_samplers(runner);
 
-    if (!(*vs_blob = compile_hlsl(&runner->r, runner->r.vs_source, "vs")))
-        return false;
+    *vs_blob = compile_hlsl(&runner->r, runner->r.vs_source, "vs");
+    fs_blob = compile_hlsl(&runner->r, runner->r.ps_source, "ps");
+    succeeded = *vs_blob && fs_blob;
 
-    if (!(fs_blob = compile_hlsl(&runner->r, runner->r.ps_source, "ps")))
+    if (runner->r.hs_source)
     {
-        ID3D10Blob_Release(*vs_blob);
+        hs_blob = compile_hlsl(&runner->r, runner->r.hs_source, "hs");
+        succeeded = succeeded && hs_blob;
+    }
+    if (runner->r.ds_source)
+    {
+        ds_blob = compile_hlsl(&runner->r, runner->r.ds_source, "ds");
+        succeeded = succeeded && ds_blob;
+    }
+
+    if (!succeeded)
+    {
+        if (*vs_blob)
+            ID3D10Blob_Release(*vs_blob);
+        if (fs_blob)
+            ID3D10Blob_Release(fs_blob);
+        if (hs_blob)
+            ID3D10Blob_Release(hs_blob);
+        if (ds_blob)
+            ID3D10Blob_Release(ds_blob);
         return false;
     }
 
@@ -837,6 +857,13 @@ static GLuint compile_graphics_shader_program(struct gl_runner *runner, ID3D10Bl
     }
     ID3D10Blob_Release(fs_blob);
 
+    /* TODO: compile and use the hs and ds blobs too, but currently this
+     * point is not reached because compile_hlsl() fails on these. */
+    if (hs_blob)
+        ID3D10Blob_Release(hs_blob);
+    if (ds_blob)
+        ID3D10Blob_Release(ds_blob);
+
     vs_id = glCreateShader(GL_VERTEX_SHADER);
     if (runner->language == SPIR_V)
     {
diff --git a/tests/shader_runner_vulkan.c b/tests/shader_runner_vulkan.c
index a3bbf170c2d8778795deae336b09b075e5b08fce..c903c8941ae0efbb5f1dc4c66aa69fc664c84080 100644
--- a/tests/shader_runner_vulkan.c
+++ b/tests/shader_runner_vulkan.c
@@ -683,9 +683,10 @@ static VkPipeline create_graphics_pipeline(struct vulkan_shader_runner *runner,
     static const VkRect2D rt_rect = {.extent.width = RENDER_TARGET_WIDTH, .extent.height = RENDER_TARGET_HEIGHT};
     VkGraphicsPipelineCreateInfo pipeline_desc = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
     VkPipelineColorBlendAttachmentState attachment_desc[MAX_RESOURCES] = {0};
+    VkPipelineTessellationStateCreateInfo tessellation_info;
     VkVertexInputAttributeDescription input_attributes[32];
     VkVertexInputBindingDescription input_bindings[32];
-    VkPipelineShaderStageCreateInfo stage_desc[2];
+    VkPipelineShaderStageCreateInfo stage_desc[4];
     struct vkd3d_shader_code vs_dxbc;
     VkDevice device = runner->device;
     VkPipeline pipeline;
@@ -696,11 +697,17 @@ static VkPipeline create_graphics_pipeline(struct vulkan_shader_runner *runner,
     memset(stage_desc, 0, sizeof(stage_desc));
     ret = create_shader_stage(runner, &stage_desc[0], "vs", VK_SHADER_STAGE_VERTEX_BIT, runner->r.vs_source, &vs_dxbc)
             && create_shader_stage(runner, &stage_desc[1], "ps", VK_SHADER_STAGE_FRAGMENT_BIT, runner->r.ps_source, NULL);
+
+    if (runner->r.hs_source)
+    {
+        ret &= create_shader_stage(runner, &stage_desc[1], "hs", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, runner->r.hs_source, NULL);
+        ret &= create_shader_stage(runner, &stage_desc[2], "ds", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, runner->r.ds_source, NULL);
+    }
     todo_if (runner->r.is_todo) ok(ret, "Failed to compile shaders.\n");
     if (!ret)
     {
-        VK_CALL(vkDestroyShaderModule(device, stage_desc[0].module, NULL));
-        VK_CALL(vkDestroyShaderModule(device, stage_desc[1].module, NULL));
+        for (i = 0; i < ARRAY_SIZE(stage_desc); ++i)
+            VK_CALL(vkDestroyShaderModule(device, stage_desc[i].module, NULL));
         return VK_NULL_HANDLE;
     }
 
@@ -791,11 +798,21 @@ static VkPipeline create_graphics_pipeline(struct vulkan_shader_runner *runner,
     pipeline_desc.renderPass = render_pass;
     pipeline_desc.subpass = 0;
 
+    if (runner->r.hs_source)
+    {
+        tessellation_info.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
+        tessellation_info.pNext = NULL;
+        tessellation_info.flags = 0;
+        tessellation_info.patchControlPoints
+                = max(primitive_topology - D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + 1, 1);
+        pipeline_desc.pTessellationState = &tessellation_info;
+    }
+
     vr = VK_CALL(vkCreateGraphicsPipelines(runner->device, VK_NULL_HANDLE, 1, &pipeline_desc, NULL, &pipeline));
     ok(vr == VK_SUCCESS, "Failed to create graphics pipeline, vr %d.\n", vr);
 
-    VK_CALL(vkDestroyShaderModule(device, stage_desc[0].module, NULL));
-    VK_CALL(vkDestroyShaderModule(device, stage_desc[1].module, NULL));
+    for (i = 0; i < ARRAY_SIZE(stage_desc); ++i)
+        VK_CALL(vkDestroyShaderModule(device, stage_desc[i].module, NULL));
     vkd3d_shader_free_scan_signature_info(&runner->vs_signatures);
     vkd3d_shader_free_shader_code(&vs_dxbc);