]> git.tdb.fi Git - libs/gl.git/blobdiff - source/glsl/spirv.cpp
Check the flat qualifier from the correct member
[libs/gl.git] / source / glsl / spirv.cpp
index 07d4eb072c0989eb1745405aae0c85ea2ae59fc7..4dceb9719a638c8ba6048b1a3509eb5b8555046c 100644 (file)
@@ -111,9 +111,14 @@ const SpirVGenerator::BuiltinFunctionInfo SpirVGenerator::builtin_functions[] =
        { "textureSize", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
        { "textureQueryLod", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
        { "textureQueryLevels", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "textureSamples", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
        { "texture", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture },
        { "textureLod", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture },
-       { "texelFetch", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texel_fetch },
+       { "texelFetch", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_fetch },
+       { "imageSize", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "imageSamples", "", "", 0, { }, CAP_IMAGE_QUERY, &SpirVGenerator::visit_builtin_texture_query },
+       { "imageLoad", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_fetch },
+       { "imageStore", "", "", 0, { }, 0, &SpirVGenerator::visit_builtin_texture_store },
        { "EmitVertex", "", "", OP_EMIT_VERTEX, { }, 0, 0 },
        { "EndPrimitive", "", "", OP_END_PRIMITIVE, { }, 0, 0 },
        { "dFdx", "f", "", OP_DP_DX, { 1 }, 0, 0 },
@@ -182,6 +187,14 @@ SpirVGenerator::BuiltinSemantic SpirVGenerator::get_builtin_semantic(const strin
                return BUILTIN_INVOCATION_ID;
        else if(name=="gl_Layer")
                return BUILTIN_LAYER;
+       else if(name=="gl_TessLevelOuter")
+               return BUILTIN_TESS_LEVEL_OUTER;
+       else if(name=="gl_TessLevelInner")
+               return BUILTIN_TESS_LEVEL_INNER;
+       else if(name=="gl_TessCoord")
+               return BUILTIN_TESS_COORD;
+       else if(name=="gl_PatchVerticesIn")
+               return BUILTIN_PATCH_VERTICES;
        else if(name=="gl_FragCoord")
                return BUILTIN_FRAG_COORD;
        else if(name=="gl_PointCoord")
@@ -194,10 +207,66 @@ SpirVGenerator::BuiltinSemantic SpirVGenerator::get_builtin_semantic(const strin
                return BUILTIN_SAMPLE_POSITION;
        else if(name=="gl_FragDepth")
                return BUILTIN_FRAG_DEPTH;
+       else if(name=="gl_NumWorkGroups")
+               return BUILTIN_NUM_WORKGROUPS;
+       else if(name=="gl_WorkGroupSize")
+               return BUILTIN_WORKGROUP_SIZE;
+       else if(name=="gl_WorkGroupID")
+               return BUILTIN_WORKGROUP_ID;
+       else if(name=="gl_LocalInvocationID")
+               return BUILTIN_LOCAL_INVOCATION_ID;
+       else if(name=="gl_GlobalInvocationID")
+               return BUILTIN_GLOBAL_INVOCATION_ID;
+       else if(name=="gl_LocalInvocationIndex")
+               return BUILTIN_LOCAL_INVOCATION_INDEX;
        else
                throw invalid_argument("SpirVGenerator::get_builtin_semantic");
 }
 
+SpirVFormat SpirVGenerator::get_format(const std::string &name)
+{
+       if(name.empty())
+               return FORMAT_UNKNOWN;
+       else if(name=="rgba32f")
+               return FORMAT_RGBA32F;
+       else if(name=="rgba16f")
+               return FORMAT_RGBA16F;
+       else if(name=="r32f")
+               return FORMAT_R32F;
+       else if(name=="rgba8")
+               return FORMAT_RGBA8;
+       else if(name=="rgba8_snorm")
+               return FORMAT_RGBA8_SNORM;
+       else if(name=="rg32f")
+               return FORMAT_RG32F;
+       else if(name=="rg16f")
+               return FORMAT_RG16F;
+       else if(name=="r16f")
+               return FORMAT_R16F;
+       else if(name=="rgba16")
+               return FORMAT_RGBA16;
+       else if(name=="rg16")
+               return FORMAT_RG16;
+       else if(name=="rg8")
+               return FORMAT_RG8;
+       else if(name=="r16")
+               return FORMAT_RG16;
+       else if(name=="r8")
+               return FORMAT_RG8;
+       else if(name=="rgba16_snorm")
+               return FORMAT_RGBA16_SNORM;
+       else if(name=="rg16_snorm")
+               return FORMAT_RG16_SNORM;
+       else if(name=="rg8_snorm")
+               return FORMAT_RG8_SNORM;
+       else if(name=="r16_snorm")
+               return FORMAT_RG16_SNORM;
+       else if(name=="r8_snorm")
+               return FORMAT_RG8_SNORM;
+       else
+               throw invalid_argument("SpirVGenerator::get_format");
+}
+
 void SpirVGenerator::use_capability(Capability cap)
 {
        if(used_capabilities.count(cap))
@@ -375,17 +444,13 @@ SpirVGenerator::Id SpirVGenerator::get_variable_type_id(const VariableDeclaratio
        if(const BasicTypeDeclaration *basic = dynamic_cast<const BasicTypeDeclaration *>(var.type_declaration))
                if(basic->kind==BasicTypeDeclaration::ARRAY)
                {
-                       Id size_id = 0;
-                       if(var.array_size)
-                       {
-                               SetFlag set_const(constant_expression);
-                               r_expression_result_id = 0;
-                               var.array_size->visit(*this);
-                               size_id = r_expression_result_id;
-                       }
-                       else
-                               size_id = get_constant_id(get_standard_type_id(BasicTypeDeclaration::INT, 1), 1);
-                       return get_array_type_id(*basic->base_type, size_id, true);
+                       if(!var.array_size)
+                               throw logic_error("array without size");
+
+                       SetFlag set_const(constant_expression);
+                       r_expression_result_id = 0;
+                       var.array_size->visit(*this);
+                       return get_array_type_id(*basic->base_type, r_expression_result_id, basic->extended_alignment);
                }
 
        return get_id(*var.type_declaration);
@@ -522,7 +587,9 @@ void SpirVGenerator::visit(VariableReference &var)
        if(composite_access)
        {
                r_expression_result_id = 0;
-               if(!assignment_source_id)
+               if(assignment_source_id)
+                       variable_load_ids.erase(var.declaration);
+               else
                {
                        auto i = variable_load_ids.find(var.declaration);
                        if(i!=variable_load_ids.end())
@@ -541,16 +608,6 @@ void SpirVGenerator::visit(VariableReference &var)
                r_expression_result_id = get_load_id(*var.declaration);
 }
 
-void SpirVGenerator::visit(InterfaceBlockReference &iface)
-{
-       if(!composite_access || !current_function)
-               throw internal_error("invalid interface block reference");
-
-       r_composite_base = iface.declaration;
-       r_expression_result_id = 0;
-       r_constant_result = false;
-}
-
 void SpirVGenerator::generate_composite_access(TypeDeclaration &result_type)
 {
        Opcode opcode;
@@ -860,9 +917,10 @@ void SpirVGenerator::visit(BinaryExpression &binary)
 
                                compare_id = write_construct(bool_vec_type_id, column_ids, n_elems);
                        }
+                       else
+                               throw internal_error("unsupported types for non-scalar equality comparison");
 
-                       if(compare_id)
-                               r_expression_result_id = write_expression(combine_op, result_type_id, compare_id);
+                       r_expression_result_id = write_expression(combine_op, result_type_id, compare_id);
                        return;
                }
        }
@@ -1035,18 +1093,22 @@ void SpirVGenerator::visit(TernaryExpression &ternary)
        writer.write_op(content.function_body, OP_SELECTION_MERGE, merge_block_id, 0);  // Selection control (none)
        writer.write_op(content.function_body, OP_BRANCH_CONDITIONAL, condition_id, true_label_id, false_label_id);
 
+       std::map<const VariableDeclaration *, Id> saved_load_ids = variable_load_ids;
+
        writer.write_op_label(true_label_id);
        ternary.true_expr->visit(*this);
        Id true_result_id = r_expression_result_id;
        true_label_id = writer.get_current_block();
        writer.write_op(content.function_body, OP_BRANCH, merge_block_id);
 
+       swap(saved_load_ids, variable_load_ids);
        writer.write_op_label(false_label_id);
        ternary.false_expr->visit(*this);
        Id false_result_id = r_expression_result_id;
        false_label_id = writer.get_current_block();
 
        writer.write_op_label(merge_block_id);
+       prune_loads(true_label_id);
        r_expression_result_id = begin_expression(OP_PHI, get_id(*ternary.type), 4);
        writer.write(true_result_id);
        writer.write(true_label_id);
@@ -1301,17 +1363,21 @@ void SpirVGenerator::visit_builtin_texture_query(FunctionCall &call, const vecto
        Opcode opcode;
        if(call.name=="textureSize")
                opcode = OP_IMAGE_QUERY_SIZE_LOD;
+       else if(call.name=="imageSize")
+               opcode = OP_IMAGE_QUERY_SIZE;
        else if(call.name=="textureQueryLod")
                opcode = OP_IMAGE_QUERY_LOD;
        else if(call.name=="textureQueryLevels")
                opcode = OP_IMAGE_QUERY_LEVELS;
+       else if(call.name=="textureSamples" || call.name=="imageSamples")
+               opcode = OP_IMAGE_QUERY_SAMPLES;
        else
                throw internal_error("invalid texture query call");
 
        ImageTypeDeclaration &image_arg0 = dynamic_cast<ImageTypeDeclaration &>(*call.arguments[0]->type);
 
        Id image_id;
-       if(image_arg0.sampled)
+       if(image_arg0.sampled && opcode!=OP_IMAGE_QUERY_LOD)
        {
                Id image_type_id = get_item(image_type_ids, get_id(image_arg0));
                image_id = write_expression(OP_IMAGE, image_type_id, argument_ids[0]);
@@ -1376,17 +1442,63 @@ void SpirVGenerator::visit_builtin_texture(FunctionCall &call, const vector<Id>
        end_expression(opcode);
 }
 
-void SpirVGenerator::visit_builtin_texel_fetch(FunctionCall &call, const vector<Id> &argument_ids)
+void SpirVGenerator::visit_builtin_texture_fetch(FunctionCall &call, const vector<Id> &argument_ids)
+{
+       ImageTypeDeclaration &image = dynamic_cast<ImageTypeDeclaration &>(*call.arguments[0]->type);
+
+       Opcode opcode;
+       if(call.name=="texelFetch")
+               opcode = OP_IMAGE_FETCH;
+       else if(call.name=="imageLoad")
+               opcode = OP_IMAGE_READ;
+       else
+               throw internal_error("invalid texture fetch call");
+
+       bool need_sample = image.multisample;
+       bool need_lod = (opcode==OP_IMAGE_FETCH && !need_sample);
+
+       if(argument_ids.size()!=2U+need_sample+need_lod)
+               throw internal_error("invalid texture fetch call");
+
+       Id image_id;
+       if(image.sampled)
+       {
+               Id image_type_id = get_item(image_type_ids, get_id(image));
+               image_id = write_expression(OP_IMAGE, image_type_id, argument_ids[0]);
+       }
+       else
+               image_id = argument_ids[0];
+
+       r_expression_result_id = begin_expression(opcode, get_id(*call.type), 2+(need_lod|need_sample)+need_lod+need_sample);
+       writer.write(image_id);
+       writer.write(argument_ids[1]);
+       if(need_lod || need_sample)
+       {
+               writer.write(need_lod*0x02 | need_sample*0x40);
+               writer.write(argument_ids.back());
+       }
+       end_expression(opcode);
+}
+
+void SpirVGenerator::visit_builtin_texture_store(FunctionCall &call, const vector<Id> &argument_ids)
 {
        if(argument_ids.size()!=3)
-               throw internal_error("invalid texelFetch call");
+               throw internal_error("invalid texture store call");
 
-       r_expression_result_id = begin_expression(OP_IMAGE_FETCH, get_id(*call.type), 4);
+       const ImageTypeDeclaration &image = dynamic_cast<const ImageTypeDeclaration &>(*call.arguments[0]->type);
+
+       begin_expression(OP_IMAGE_WRITE, get_id(*call.type), 3+image.multisample*2);
        for(unsigned i=0; i<2; ++i)
                writer.write(argument_ids[i]);
-       writer.write(2);  // Lod
        writer.write(argument_ids.back());
-       end_expression(OP_IMAGE_FETCH);
+       if(image.multisample)
+       {
+               writer.write(0x40);  // Sample
+               writer.write(argument_ids[2]);
+       }
+       end_expression(OP_IMAGE_WRITE);
+
+       r_expression_result_id = 0;
 }
 
 void SpirVGenerator::visit_builtin_interpolate(FunctionCall &call, const vector<Id> &argument_ids)
@@ -1494,6 +1606,7 @@ void SpirVGenerator::visit(ImageTypeDeclaration &image)
                return;
 
        Id type_id = allocate_id(image, 0);
+       SpirVFormat format = get_format(image.format);
 
        Id image_id = (image.sampled ? next_id++ : type_id);
        writer.begin_op(content.globals, OP_TYPE_IMAGE, 9);
@@ -1502,9 +1615,9 @@ void SpirVGenerator::visit(ImageTypeDeclaration &image)
        writer.write(image.dimensions-1);
        writer.write(image.shadow);
        writer.write(image.array);
-       writer.write(false);  // Multisample
+       writer.write(image.multisample);
        writer.write(image.sampled ? 1 : 2);
-       writer.write(0);  // Format (unknown)
+       writer.write(format);
        writer.end_op(OP_TYPE_IMAGE);
 
        if(image.sampled)
@@ -1518,6 +1631,12 @@ void SpirVGenerator::visit(ImageTypeDeclaration &image)
                use_capability(image.sampled ? CAP_SAMPLED_1D : CAP_IMAGE_1D);
        else if(image.dimensions==ImageTypeDeclaration::CUBE && image.array)
                use_capability(image.sampled ? CAP_SAMPLED_CUBE_ARRAY : CAP_IMAGE_CUBE_ARRAY);
+
+       if(image.multisample && !image.sampled)
+               use_capability(CAP_STORAGE_IMAGE_MULTISAMPLE);
+
+       if(format>=FORMAT_RG32F && format<=FORMAT_R8_SNORM)
+               use_capability(CAP_STORAGE_IMAGE_EXTENDED_FORMATS);
 }
 
 void SpirVGenerator::visit(StructDeclaration &strct)
@@ -1526,12 +1645,12 @@ void SpirVGenerator::visit(StructDeclaration &strct)
                return;
 
        Id type_id = allocate_id(strct, 0);
-       writer.write_op_name(type_id, strct.name);
+       writer.write_op_name(type_id, (strct.block_name.empty() ? strct.name : strct.block_name));
 
-       if(strct.interface_block)
+       if(!strct.block_name.empty())
                writer.write_op_decorate(type_id, DECO_BLOCK);
 
-       bool builtin = (strct.interface_block && !strct.interface_block->block_name.compare(0, 3, "gl_"));
+       bool builtin = !strct.block_name.compare(0, 3, "gl_");
        vector<Id> member_type_ids;
        member_type_ids.reserve(strct.members.body.size());
        for(const RefPtr<Statement> &s: strct.members.body)
@@ -1615,11 +1734,20 @@ void SpirVGenerator::visit(VariableDeclaration &var)
        }
        else
        {
-               StorageClass storage = (current_function ? STORAGE_FUNCTION : get_interface_storage(var.interface, false));
+               bool push_const = has_layout_qualifier(var.layout.get(), "push_constant");
+
+               StorageClass storage;
+               if(current_function)
+                       storage = STORAGE_FUNCTION;
+               else if(push_const)
+                       storage = STORAGE_PUSH_CONSTANT;
+               else
+                       storage = get_interface_storage(var.interface, var.block_declaration);
+
                Id ptr_type_id = get_pointer_type_id(type_id, storage);
                if(var.interface=="uniform")
                {
-                       Id &uni_id = declared_uniform_ids["v"+var.name];
+                       Id &uni_id = declared_uniform_ids[var.block_declaration ? "b"+var.block_declaration->block_name : "v"+var.name];
                        if(uni_id)
                        {
                                insert_unique(declared_ids, &var, Declaration(uni_id, ptr_type_id));
@@ -1662,11 +1790,17 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                                        writer.write_op_decorate(var_id, DECO_BINDING, q.value);
                        }
                }
-               if(!var.name.compare(0, 3, "gl_"))
+               if(!var.block_declaration && !var.name.compare(0, 3, "gl_"))
                {
                        BuiltinSemantic semantic = get_builtin_semantic(var.name);
                        writer.write_op_decorate(var_id, DECO_BUILTIN, semantic);
                }
+               if(var.interpolation=="flat")
+                       writer.write_op_decorate(var_id, DECO_FLAT);
+               if(var.sampling=="centroid")
+                       writer.write_op_decorate(var_id, DECO_CENTROID);
+               if(var.sampling=="patch")
+                       writer.write_op_decorate(var_id, DECO_PATCH);
 
                if(init_id && current_function)
                {
@@ -1675,49 +1809,8 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                }
        }
 
-       writer.write_op_name(var_id, var.name);
-}
-
-void SpirVGenerator::visit(InterfaceBlock &iface)
-{
-       bool push_const = has_layout_qualifier(iface.layout.get(), "push_constant");
-
-       StorageClass storage = (push_const ? STORAGE_PUSH_CONSTANT : get_interface_storage(iface.interface, true));
-       Id type_id;
-       if(iface.array)
-               type_id = get_array_type_id(*iface.struct_declaration, 0, true);
-       else
-               type_id = get_id(*iface.struct_declaration);
-       Id ptr_type_id = get_pointer_type_id(type_id, storage);
-
-       Id block_id;
-       if(iface.interface=="uniform")
-       {
-               Id &uni_id = declared_uniform_ids["b"+iface.block_name];
-               if(uni_id)
-               {
-                       insert_unique(declared_ids, &iface, Declaration(uni_id, ptr_type_id));
-                       return;
-               }
-
-               uni_id = block_id = allocate_id(iface, ptr_type_id);
-       }
-       else
-               block_id = allocate_id(iface, ptr_type_id);
-       writer.write_op_name(block_id, iface.instance_name);
-
-       writer.write_op(content.globals, OP_VARIABLE, ptr_type_id, block_id, storage);
-
-       if(iface.layout)
-       {
-               for(const Layout::Qualifier &q: iface.layout->qualifiers)
-               {
-                       if(q.name=="set")
-                               writer.write_op_decorate(block_id, DECO_DESCRIPTOR_SET, q.value);
-                       else if(q.name=="binding")
-                               writer.write_op_decorate(block_id, DECO_BINDING, q.value);
-               }
-       }
+       if(var.name.find(' ')==string::npos)
+               writer.write_op_name(var_id, var.name);
 }
 
 void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
@@ -1726,8 +1819,11 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
        switch(stage->type)
        {
        case Stage::VERTEX: writer.write(0); break;
+       case Stage::TESS_CONTROL: writer.write(1); break;
+       case Stage::TESS_EVAL: writer.write(2); break;
        case Stage::GEOMETRY: writer.write(3); break;
        case Stage::FRAGMENT: writer.write(4); break;
+       case Stage::COMPUTE: writer.write(5); break;
        default: throw internal_error("unknown stage");
        }
        writer.write(func_id);
@@ -1735,15 +1831,9 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
 
        set<Node *> dependencies = DependencyCollector().apply(func);
        for(Node *n: dependencies)
-       {
                if(const VariableDeclaration *var = dynamic_cast<const VariableDeclaration *>(n))
-               {
                        if(!var->interface.empty())
-                               writer.write(get_id(*n));
-               }
-               else if(dynamic_cast<InterfaceBlock *>(n))
-                       writer.write(get_id(*n));
-       }
+                               writer.write(allocate_forward_id(*n));
 
        writer.end_op(OP_ENTRY_POINT);
 
@@ -1753,7 +1843,16 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, origin);
        }
        else if(stage->type==Stage::GEOMETRY)
+       {
                use_capability(CAP_GEOMETRY);
+               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INVOCATIONS, 1);
+       }
+       else if(stage->type==Stage::TESS_CONTROL || stage->type==Stage::TESS_EVAL)
+       {
+               use_capability(CAP_TESSELLATION);
+       }
+
+       unsigned local_size[3] = { 0, 1, 1 };
 
        for(const InterfaceLayout *i: interface_layouts)
        {
@@ -1770,14 +1869,44 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_TRIANGLES);
                        else if(q.name=="triangles_adjacency")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_INPUT_TRIANGLES_ADJACENCY);
+                       else if(q.name=="quads")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_QUADS);
+                       else if(q.name=="isolines")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_ISOLINES);
                        else if(q.name=="line_strip")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_LINE_STRIP);
                        else if(q.name=="triangle_strip")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_TRIANGLE_STRIP);
-                       else if(q.name=="max_vertices")
+                       else if(q.name=="max_vertices" || q.name=="vertices")
                                writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_OUTPUT_VERTICES, q.value);
+                       else if(q.name=="cw")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_VERTEX_ORDER_CW);
+                       else if(q.name=="ccw")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_VERTEX_ORDER_CCW);
+                       else if(q.name=="equal_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_EQUAL);
+                       else if(q.name=="fractional_even_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_FRACTIONAL_EVEN);
+                       else if(q.name=="fractional_odd_spacing")
+                               writer.write_op(content.exec_modes, OP_EXECUTION_MODE, func_id, EXEC_SPACING_FRACTIONAL_ODD);
+                       else if(q.name=="local_size_x")
+                               local_size[0] = q.value;
+                       else if(q.name=="local_size_y")
+                               local_size[1] = q.value;
+                       else if(q.name=="local_size_z")
+                               local_size[2] = q.value;
                }
        }
+
+       if(stage->type==Stage::COMPUTE && local_size[0])
+       {
+               writer.begin_op(content.exec_modes, OP_EXECUTION_MODE);
+               writer.write(func_id);
+               writer.write(EXEC_LOCAL_SIZE);
+               for(unsigned j=0; j<3; ++j)
+                       writer.write(local_size[j]);
+               writer.end_op(OP_EXECUTION_MODE);
+       }
 }
 
 void SpirVGenerator::visit(FunctionDeclaration &func)
@@ -1829,6 +1958,7 @@ void SpirVGenerator::visit(FunctionDeclaration &func)
        {
                Id param_id = allocate_id(*func.parameters[i], param_type_ids[i]);
                writer.write_op(content.functions, OP_FUNCTION_PARAMETER, param_type_ids[i], param_id);
+               writer.write_op_name(param_id, func.parameters[i]->name);
                // TODO This is probably incorrect if the parameter is assigned to.
                variable_load_ids[func.parameters[i].get()] = param_id;
        }