diff --git a/Makefile.am b/Makefile.am
index f6e105e072684a9b6b8cbc7ccb804ce0db8aeda4..b8e4aa4bbd7f7a71765e098892340b395ce7eb14 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -235,6 +235,7 @@ vkd3d_shader_tests = \
 	tests/hlsl/strings.shader_test \
 	tests/hlsl/struct-array.shader_test \
 	tests/hlsl/struct-assignment.shader_test \
+	tests/hlsl/struct-inheritance.shader_test \
 	tests/hlsl/struct-semantics.shader_test \
 	tests/hlsl/switch.shader_test \
 	tests/hlsl/swizzle-constant-prop.shader_test \
diff --git a/libs/vkd3d-shader/hlsl.y b/libs/vkd3d-shader/hlsl.y
index da8a1b870f3caf2870fd086fe1fab8efc9044320..ee13e193d4978da67f37728f2cd9f4c2d6346a22 100644
--- a/libs/vkd3d-shader/hlsl.y
+++ b/libs/vkd3d-shader/hlsl.y
@@ -1208,6 +1208,32 @@ static bool gen_struct_fields(struct hlsl_ctx *ctx, struct parse_fields *fields,
     return true;
 }
 
+static bool add_record_access_recurse(struct hlsl_ctx *ctx, struct hlsl_block *block,
+        const char *name, const struct vkd3d_shader_location *loc)
+{
+    struct hlsl_ir_node *record = node_from_block(block);
+    const struct hlsl_type *type = record->data_type;
+    const struct hlsl_struct_field *field, *base;
+
+    if ((field = get_struct_field(type->e.record.fields, type->e.record.field_count, name)))
+    {
+        unsigned int field_idx = field - type->e.record.fields;
+
+        return add_record_access(ctx, block, record, field_idx, loc);
+    }
+    else if ((base = get_struct_field(type->e.record.fields, type->e.record.field_count, "$super")))
+    {
+        unsigned int base_idx = base - type->e.record.fields;
+
+        if (!add_record_access(ctx, block, record, base_idx, loc))
+            return false;
+        return add_record_access_recurse(ctx, block, name, loc);
+    }
+
+    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Field \"%s\" is not defined.", name);
+    return false;
+}
+
 static bool add_typedef(struct hlsl_ctx *ctx, struct hlsl_type *const orig_type, struct list *list)
 {
     struct parse_variable_def *v, *v_next;
@@ -6590,6 +6616,7 @@ static void validate_uav_type(struct hlsl_ctx *ctx, enum hlsl_sampler_dim dim,
 
 %type <switch_case> switch_case
 
+%type <type> base_optional
 %type <type> field_type
 %type <type> named_struct_spec
 %type <type> unnamed_struct_spec
@@ -6804,11 +6831,28 @@ struct_spec:
     | unnamed_struct_spec
 
 named_struct_spec:
-      KW_STRUCT any_identifier '{' fields_list '}'
+      KW_STRUCT any_identifier base_optional '{' fields_list '}'
         {
             bool ret;
 
-            $$ = hlsl_new_struct_type(ctx, $2, $4.fields, $4.count);
+            if ($3)
+            {
+                char *name;
+
+                if (!(name = hlsl_strdup(ctx, "$super")))
+                    YYABORT;
+                if (!hlsl_array_reserve(ctx, (void **)&$5.fields, &$5.capacity, 1 + $5.count, sizeof(*$5.fields)))
+                    YYABORT;
+                memmove(&$5.fields[1], $5.fields, $5.count * sizeof(*$5.fields));
+                ++$5.count;
+
+                memset(&$5.fields[0], 0, sizeof($5.fields[0]));
+                $5.fields[0].type = $3;
+                $5.fields[0].loc = @3;
+                $5.fields[0].name = name;
+            }
+
+            $$ = hlsl_new_struct_type(ctx, $2, $5.fields, $5.count);
 
             if (hlsl_get_var(ctx->cur_scope, $2))
             {
@@ -6835,6 +6879,23 @@ any_identifier:
     | TYPE_IDENTIFIER
     | NEW_IDENTIFIER
 
+/* TODO: Multiple inheritance support for interfaces. */
+base_optional:
+      %empty
+        {
+            $$ = NULL;
+        }
+    | ':' TYPE_IDENTIFIER
+        {
+            $$ = hlsl_get_type(ctx->cur_scope, $2, true, true);
+            if ($$->class != HLSL_CLASS_STRUCT)
+            {
+                hlsl_error(ctx, &@2, VKD3D_SHADER_ERROR_HLSL_INVALID_TYPE, "Base type \"%s\" is not a struct.", $2);
+                YYABORT;
+            }
+            vkd3d_free($2);
+        }
+
 fields_list:
       %empty
         {
@@ -8825,19 +8886,7 @@ postfix_expr:
 
             if (node->data_type->class == HLSL_CLASS_STRUCT)
             {
-                struct hlsl_type *type = node->data_type;
-                const struct hlsl_struct_field *field;
-                unsigned int field_idx = 0;
-
-                if (!(field = get_struct_field(type->e.record.fields, type->e.record.field_count, $3)))
-                {
-                    hlsl_error(ctx, &@3, VKD3D_SHADER_ERROR_HLSL_NOT_DEFINED, "Field \"%s\" is not defined.", $3);
-                    vkd3d_free($3);
-                    YYABORT;
-                }
-
-                field_idx = field - type->e.record.fields;
-                if (!add_record_access(ctx, $1, node, field_idx, &@2))
+                if (!add_record_access_recurse(ctx, $1, $3, &@2))
                 {
                     vkd3d_free($3);
                     YYABORT;
diff --git a/tests/hlsl/struct-inheritance.shader_test b/tests/hlsl/struct-inheritance.shader_test
new file mode 100644
index 0000000000000000000000000000000000000000..581d743dad1d6f658d22e8b86026b5b3ec0b3a02
--- /dev/null
+++ b/tests/hlsl/struct-inheritance.shader_test
@@ -0,0 +1,134 @@
+[pixel shader]
+
+struct a
+{
+    float4 aa;
+    float4 bb;
+};
+
+struct b : a
+{
+    float4 cc;
+};
+
+struct c : b
+{
+    float4 bb; // Shadows a.bb
+};
+
+c data;
+
+float4 main() : sv_target
+{
+    return data.aa + data.bb + data.cc;
+}
+
+[test]
+uniform 0  float4 1 0 0 0
+uniform 4  float4 0 2 0 0
+uniform 8  float4 0 0 3 0
+uniform 12 float4 0 0 0 4
+draw quad
+probe (0, 0) rgba (1, 0, 3, 4)
+
+% Test writing to a field derived from a base class.
+
+[pixel shader]
+
+struct a
+{
+    float4 aa;
+    float4 bb;
+};
+
+struct b : a
+{
+    float4 cc;
+};
+
+struct c : b
+{
+    float4 bb; // Shadows a.bb
+};
+
+c data;
+
+float4 main() : sv_target
+{
+    c data2 = data;
+    data2.aa = float4(-1, 0, 0, 0);
+    data2.bb = float4(0, 0, 0, -4);
+    return data2.aa + data2.bb + data2.cc;
+}
+
+[test]
+uniform 0  float4 1 0 0 0
+uniform 4  float4 0 2 0 0
+uniform 8  float4 0 0 3 0
+uniform 12 float4 0 0 0 4
+draw quad
+probe (0, 0) rgba (-1, 0, 3, -4)
+
+
+% Multiple concrete base types are not allowed.
+
+[pixel shader fail]
+
+struct a
+{
+    float4 aa;
+};
+
+struct b
+{
+    float4 bb;
+};
+
+struct c : a, b
+{
+    float4 cc;
+};
+
+float4 main() : sv_target
+{
+    return 0;
+}
+
+
+% The concrete base type must be a struct.
+
+[pixel shader fail]
+
+struct a : float4
+{
+    float4 aa;
+};
+
+float4 main() : sv_target
+{
+    return 0;
+}
+
+[pixel shader fail]
+
+struct a : vector<float, 4>
+{
+    float4 aa;
+};
+
+float4 main() : sv_target
+{
+    return 0;
+}
+
+[pixel shader fail]
+
+struct a : Buffer<float>
+{
+    float4 aa;
+};
+
+float4 main() : sv_target
+{
+    return 0;
+}