]> git.tdb.fi Git - libs/gl.git/commitdiff
Implement tessellation shaders in the shader compiler
authorMikko Rasa <tdb@tdb.fi>
Sat, 3 Sep 2022 13:14:43 +0000 (16:14 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 3 Sep 2022 13:38:44 +0000 (16:38 +0300)
12 files changed:
builtin_data/_builtin.glsl
source/glsl/compiler.cpp
source/glsl/finalize.cpp
source/glsl/generate.cpp
source/glsl/generate.h
source/glsl/parser.cpp
source/glsl/preprocessor.cpp
source/glsl/spirv.cpp
source/glsl/spirvconstants.h
source/glsl/syntax.cpp
source/glsl/syntax.h
source/glsl/validate.cpp

index 51035d32478466f8642c0a21a8963c9156781c75..dcbb6d0abc7753c07cfc5cafbce39ce27b59e013 100644 (file)
@@ -582,6 +582,44 @@ out gl_PerVertex
   float gl_ClipDistance[];
 };
 
+#pragma MSP stage(tess_control)
+in gl_PerVertex
+{
+  vec4 gl_Position;
+  float gl_PointSize;
+  float gl_ClipDistance[];
+} gl_in[];
+in int gl_PatchVerticesIn;
+in int gl_PrimitiveID;
+in int gl_InvocationID;
+out gl_PerVertex
+{
+  vec4 gl_Position;
+  float gl_PointSize;
+  float gl_ClipDistance[];
+} gl_out[];
+patch out float gl_TessLevelOuter[4];
+patch out float gl_TessLevelInner[2];
+
+#pragma MSP stage(tess_eval)
+in gl_PerVertex
+{
+  vec4 gl_Position;
+  float gl_PointSize;
+  float gl_ClipDistance[];
+} gl_in[];
+in int gl_PatchVerticesIn;
+in int gl_PrimitiveID;
+in vec3 gl_TessCoord;
+patch in float gl_TessLevelOuter[4];
+patch in float gl_TessLevelInner[2];
+out gl_PerVertex
+{
+  vec4 gl_Position;
+  float gl_PointSize;
+  float gl_ClipDistance[];
+};
+
 #pragma MSP stage(geometry)
 in gl_PerVertex
 {
index 05bf421cd422428dbf1b09b5eef3d0daf737c3f9..97c234f79285f3c253e94d76dd36b937d13e55b4 100644 (file)
@@ -318,6 +318,7 @@ void Compiler::generate(Stage &stage)
        InterfaceGenerator().apply(stage);
        resolve(stage, RESOLVE_BLOCKS|RESOLVE_TYPES|RESOLVE_VARIABLES);
 
+       LayoutDefaulter().apply(stage);
        ArraySizer().apply(stage);
        resolve(stage, RESOLVE_EXPRESSIONS);
 }
index 3b0909ac2a90949733577e0e3a22dc0383cca445..d840648bbc3ec0135bbb02b4dc47350cf753f03f 100644 (file)
@@ -471,7 +471,14 @@ void StructuralFeatureConverter::visit(RefPtr<Expression> &expr)
 
 bool StructuralFeatureConverter::supports_stage(Stage::Type st) const
 {
-       if(st==Stage::GEOMETRY)
+       if(st==Stage::TESS_CONTROL || st==Stage::TESS_EVAL)
+       {
+               if(features.target_api==OPENGL_ES)
+                       return check_version(Version(3, 20));
+               else
+                       return check_version(Version(4, 0));
+       }
+       else if(st==Stage::GEOMETRY)
        {
                if(features.target_api==OPENGL_ES)
                        return check_version(Version(3, 20));
index e4830ec22aee2340aae79a93711600f415ef89a6..cc427d05c30c3f9d66a32556d4ee2c5aa8d042d5 100644 (file)
@@ -108,8 +108,15 @@ VariableDeclaration *InterfaceGenerator::generate_interface(VariableDeclaration
        iface_var->interface = iface;
        iface_var->type = var.type;
        iface_var->name = name;
-       // Geometry shader inputs are always arrays.
-       if(stage->type==Stage::GEOMETRY)
+       // Tessellation and geometry inputs may be arrayed.
+       if(stage->type==Stage::TESS_CONTROL)
+               // VS out -> TCS in: add | TCS in -> TCS out: unchanged | VS out -> TCS out: add
+               iface_var->array = (var.array || var.interface!="in");
+       else if(stage->type==Stage::TESS_EVAL)
+               // TCS out -> TES in: unchanged | TES in -> TES out: remove | TCS out -> TES out: remove
+               iface_var->array = (var.array && iface=="in");
+       else if(stage->type==Stage::GEOMETRY)
+               // VS/TES out -> GS in: add | GS in -> GS out: remove | VS/TES out -> GS out: unchanged
                iface_var->array = ((var.array && var.interface!="in") || iface=="in");
        else
                iface_var->array = var.array;
@@ -339,6 +346,38 @@ void InterfaceGenerator::visit(Passthrough &pass)
 }
 
 
+void LayoutDefaulter::apply(Stage &stage)
+{
+       if(stage.type==Stage::TESS_EVAL)
+       {
+               stage.content.visit(*this);
+               if((need_winding || need_spacing) && in_iface)
+               {
+                       if(need_winding)
+                               in_iface->layout.qualifiers.emplace_back("ccw");
+                       if(need_spacing)
+                               in_iface->layout.qualifiers.emplace_back("equal_spacing");
+               }
+       }
+}
+
+void LayoutDefaulter::visit(InterfaceLayout &iface)
+{
+       if(iface.interface=="in")
+       {
+               if(!in_iface)
+                       in_iface = &iface;
+               for(const Layout::Qualifier &q: iface.layout.qualifiers)
+               {
+                       if(q.name=="cw" || q.name=="ccw")
+                               need_winding = false;
+                       else if(q.name=="equal_spacing" || q.name=="fractional_even_spacing" || q.name=="fractional_odd_spacing")
+                               need_spacing = false;
+               }
+       }
+}
+
+
 void ArraySizer::apply(Stage &stage)
 {
        stage.content.visit(*this);
index a4ab75719e29b2416ad36e3be6918ace0beeddd1..aba2c353f827239f7f4aaf3b2d2d73560d680db2 100644 (file)
@@ -63,6 +63,20 @@ private:
        virtual void visit(Passthrough &);
 };
 
+class LayoutDefaulter: private TraversingVisitor
+{
+private:
+       InterfaceLayout *in_iface = 0;
+       bool need_winding = true;
+       bool need_spacing = true;
+
+public:
+       void apply(Stage &);
+
+private:
+       virtual void visit(InterfaceLayout &);
+};
+
 /**
 Assigns sizes to arrays which don't have a size.  Geometry shader inputs are
 sized by topology.  Other arrays are sized by their use with literal indices.
index d210cee9fe7d97c97dc48e90ec2f3b43ec6a6209..615d75d426a38e1e2e83dabf09fee6edd23b78e7 100644 (file)
@@ -176,7 +176,7 @@ bool Parser::is_interface_qualifier(const string &token)
 
 bool Parser::is_sampling_qualifier(const string &token)
 {
-       return (token=="centroid" || token=="sample");
+       return (token=="centroid" || token=="sample" || token=="patch");
 }
 
 bool Parser::is_interpolation_qualifier(const string &token)
index f2becd91f371302133948f3156a4ab3c2b81e6fe..ca12ec6169c106bf4b4480f02218713184a08e3e 100644 (file)
@@ -89,6 +89,10 @@ void Preprocessor::preprocess_stage()
        Stage::Type stage = Stage::SHARED;
        if(token=="vertex")
                stage = Stage::VERTEX;
+       else if(token=="tess_control")
+               stage = Stage::TESS_CONTROL;
+       else if(token=="tess_eval")
+               stage = Stage::TESS_EVAL;
        else if(token=="geometry")
                stage = Stage::GEOMETRY;
        else if(token=="fragment")
index 85312bc4edb67760f2e87d0591a0e7499707d114..b2eac3f0fe1925b11cc586e3e3485d19735ca388 100644 (file)
@@ -187,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")
@@ -1775,6 +1783,8 @@ void SpirVGenerator::visit(VariableDeclaration &var)
                        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)
                {
@@ -1793,6 +1803,8 @@ 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;
@@ -1819,6 +1831,10 @@ void SpirVGenerator::visit_entry_point(FunctionDeclaration &func, Id func_id)
                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 };
 
@@ -1837,12 +1853,26 @@ 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")
index bd82becd0e54699530ed92a9d2c0bdbb6dcc91c3..e3c245fb7e75abb59e469be5e5727f0a33eda191 100644 (file)
@@ -162,6 +162,7 @@ enum SpirVCapability
 {
        CAP_SHADER = 1,
        CAP_GEOMETRY = 2,
+       CAP_TESSELLATION = 3,
        CAP_STORAGE_IMAGE_MULTISAMPLE = 27,
        CAP_IMAGE_CUBE_ARRAY = 34,
        CAP_SAMPLED_1D = 43,
@@ -175,6 +176,11 @@ enum SpirVCapability
 enum SpirVExecutionMode
 {
        EXEC_INVOCATIONS = 0,
+       EXEC_SPACING_EQUAL = 1,
+       EXEC_SPACING_FRACTIONAL_EVEN = 2,
+       EXEC_SPACING_FRACTIONAL_ODD = 3,
+       EXEC_VERTEX_ORDER_CW = 4,
+       EXEC_VERTEX_ORDER_CCW = 5,
        EXEC_ORIGIN_UPPER_LEFT = 7,
        EXEC_ORIGIN_LOWER_LEFT = 8,
        EXEC_LOCAL_SIZE = 17,
@@ -183,6 +189,8 @@ enum SpirVExecutionMode
        EXEC_INPUT_LINES_ADJACENCY = 21,
        EXEC_TRIANGLES = 22,
        EXEC_INPUT_TRIANGLES_ADJACENCY = 23,
+       EXEC_QUADS = 24,
+       EXEC_ISOLINES = 25,
        EXEC_OUTPUT_VERTICES = 26,
        EXEC_OUTPUT_POINTS = 27,
        EXEC_OUTPUT_LINE_STRIP = 28,
@@ -210,6 +218,7 @@ enum SpirVDecoration
        DECO_MATRIX_STRIDE = 7,
        DECO_BUILTIN = 11,
        DECO_FLAT = 14,
+       DECO_PATCH = 15,
        DECO_CENTROID = 16,
        DECO_LOCATION = 30,
        DECO_BINDING = 33,
@@ -227,6 +236,10 @@ enum SpirVBuiltin
        BUILTIN_PRIMITIVE_ID = 7,
        BUILTIN_INVOCATION_ID = 8,
        BUILTIN_LAYER = 9,
+       BUILTIN_TESS_LEVEL_OUTER = 11,
+       BUILTIN_TESS_LEVEL_INNER = 12,
+       BUILTIN_TESS_COORD = 13,
+       BUILTIN_PATCH_VERTICES = 14,
        BUILTIN_FRAG_COORD = 15,
        BUILTIN_POINT_COORD = 16,
        BUILTIN_FRONT_FACING = 17,
index b805a95aa47213af6942c9e377caa4a41ad70e0f..c37ad0895a86dfd09517b8a7d94d764c3fc4ed8f 100644 (file)
@@ -332,7 +332,7 @@ Stage::Stage(Stage::Type t):
 
 const char *Stage::get_stage_name(Type type)
 {
-       static const char *const names[] = { "shared", "vertex", "geometry", "fragment", "compute" };
+       static const char *const names[] = { "shared", "vertex", "tess_control", "tess_eval", "geometry", "fragment", "compute" };
        return names[type];
 }
 
index 1f43eac303bf6db2e7e6c5f705ee77a40ee23d37..ebe188e1f6afa932fe5fd697509c043cfeb5ef23 100644 (file)
@@ -489,6 +489,8 @@ struct Stage
        {
                SHARED,
                VERTEX,
+               TESS_CONTROL,
+               TESS_EVAL,
                GEOMETRY,
                FRAGMENT,
                COMPUTE
index 5a28fcb6abca0ec042dda6f41aff0f5d6067053d..ce5a85b4a54af5de0fdceed95e9c6e42a6ab8275 100644 (file)
@@ -51,7 +51,17 @@ void DeclarationValidator::apply(Stage &s, const Features &f)
                                global_err_node = j->get();
        }
 
-       if(s.type==Stage::GEOMETRY)
+       if(s.type==Stage::TESS_CONTROL)
+       {
+               if(!have_output_vertex_count)
+                       error(*global_err_node, "No vertex count qualifier found on output");
+       }
+       else if(s.type==Stage::TESS_EVAL)
+       {
+               if(!have_input_primitive)
+                       error(*global_err_node, "No primitive type qualifier found on input");
+       }
+       else if(s.type==Stage::GEOMETRY)
        {
                if(!have_input_primitive)
                        error(*global_err_node, "No primitive type qualifier found on input");
@@ -152,13 +162,27 @@ void DeclarationValidator::visit(Layout &layout)
                                        have_output_primitive = true;
                        }
                }
-               else if(q.name=="lines" || q.name=="lines_adjacency" || q.name=="triangles" || q.name=="triangles_adjacency")
+               else if(q.name=="triangles")
+               {
+                       allowed = ((stage->type==Stage::GEOMETRY || stage->type==Stage::TESS_EVAL) && iface_layout && iface_layout->interface=="in");
+                       value = false;
+                       if(allowed)
+                               have_input_primitive = true;
+               }
+               else if(q.name=="lines" || q.name=="lines_adjacency" || q.name=="triangles_adjacency")
                {
                        allowed = (stage->type==Stage::GEOMETRY && iface_layout && iface_layout->interface=="in");
                        value = false;
                        if(allowed)
                                have_input_primitive = true;
                }
+               else if(q.name=="quads" || q.name=="isolines")
+               {
+                       allowed = (stage->type==Stage::TESS_EVAL && iface_layout && iface_layout->interface=="in");
+                       value = false;
+                       if(allowed)
+                               have_input_primitive = true;
+               }
                else if(q.name=="line_strip" || q.name=="triangle_strip")
                {
                        allowed = (stage->type==Stage::GEOMETRY && iface_layout && iface_layout->interface=="out");
@@ -174,6 +198,18 @@ void DeclarationValidator::visit(Layout &layout)
                        if(allowed)
                                have_output_vertex_count = true;
                }
+               else if(q.name=="vertices")
+               {
+                       allowed = (stage->type==Stage::TESS_CONTROL && iface_layout && iface_layout->interface=="out");
+                       if(allowed)
+                               have_output_vertex_count = true;
+               }
+               else if(q.name=="cw" || q.name=="ccw" ||
+                       q.name=="equal_spacing" || q.name=="fractional_even_spacing" || q.name=="fractional_odd_spacing")
+               {
+                       allowed = (stage->type==Stage::TESS_EVAL && iface_layout && iface_layout->interface=="in");
+                       value = false;
+               }
                else if(q.name=="std140" || q.name=="std430")
                {
                        allowed = (iface_block && !variable && iface_block->interface=="uniform");
@@ -308,6 +344,10 @@ void DeclarationValidator::visit(VariableDeclaration &var)
                        error(var, "Interpolation qualifier not allowed on fragment output");
                else if((var.interface!="in" && var.interface!="out") || (scope==FUNCTION_PARAM || scope==FUNCTION))
                        error(var, "Interpolation qualifier not allowed on non-interpolated variable");
+               else if(var.sampling=="patch" && (stage->type!=Stage::TESS_CONTROL || var.interface!="out") && (stage->type!=Stage::TESS_EVAL || var.interface!="in"))
+                       error(var, "Per-patch variables only allowed on tessellation control output or tessellation evaluation input");
+               if(var.sampling=="patch" && !var.interpolation.empty())
+                       error(var, "Interpolation qualifier not allowed on per-patch variable");
        }
 
        if(!var.interface.empty())
@@ -819,7 +859,7 @@ void StageInterfaceValidator::visit(VariableDeclaration &var)
                if(var.type_declaration && var.linked_declaration->type_declaration)
                {
                        TypeDeclaration *type = var.type_declaration;
-                       if(stage->type==Stage::GEOMETRY)
+                       if(stage->type==Stage::TESS_CONTROL || stage->type==Stage::GEOMETRY)
                        {
                                if(const BasicTypeDeclaration *basic = dynamic_cast<const BasicTypeDeclaration *>(type))
                                        if(basic->kind==BasicTypeDeclaration::ARRAY && basic->base_type)
@@ -832,6 +872,12 @@ void StageInterfaceValidator::visit(VariableDeclaration &var)
                                        var.linked_declaration->name, var.linked_declaration->type_declaration->name));
                        }
                }
+               if((var.sampling=="patch") != (var.linked_declaration->sampling=="patch"))
+               {
+                       error(var, format("Mismatched sampling qualifier '%s' for 'in %s'", var.sampling, var.name));
+                       add_info(*var.linked_declaration, format("Linked to 'out %s' qualified as '%s'",
+                               var.linked_declaration->name, var.linked_declaration->sampling));
+               }
        }
 
        if(location>=0 && !var.interface.empty())