From 79db78e77054cd93e69123b3b6495a561f5cbd10 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sat, 3 Sep 2022 16:14:43 +0300 Subject: [PATCH] Implement tessellation shaders in the shader compiler --- builtin_data/_builtin.glsl | 38 ++++++++++++++++++++++++++ source/glsl/compiler.cpp | 1 + source/glsl/finalize.cpp | 9 ++++++- source/glsl/generate.cpp | 43 +++++++++++++++++++++++++++-- source/glsl/generate.h | 14 ++++++++++ source/glsl/parser.cpp | 2 +- source/glsl/preprocessor.cpp | 4 +++ source/glsl/spirv.cpp | 32 +++++++++++++++++++++- source/glsl/spirvconstants.h | 13 +++++++++ source/glsl/syntax.cpp | 2 +- source/glsl/syntax.h | 2 ++ source/glsl/validate.cpp | 52 +++++++++++++++++++++++++++++++++--- 12 files changed, 203 insertions(+), 9 deletions(-) diff --git a/builtin_data/_builtin.glsl b/builtin_data/_builtin.glsl index 51035d32..dcbb6d0a 100644 --- a/builtin_data/_builtin.glsl +++ b/builtin_data/_builtin.glsl @@ -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 { diff --git a/source/glsl/compiler.cpp b/source/glsl/compiler.cpp index 05bf421c..97c234f7 100644 --- a/source/glsl/compiler.cpp +++ b/source/glsl/compiler.cpp @@ -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); } diff --git a/source/glsl/finalize.cpp b/source/glsl/finalize.cpp index 3b0909ac..d840648b 100644 --- a/source/glsl/finalize.cpp +++ b/source/glsl/finalize.cpp @@ -471,7 +471,14 @@ void StructuralFeatureConverter::visit(RefPtr &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)); diff --git a/source/glsl/generate.cpp b/source/glsl/generate.cpp index e4830ec2..cc427d05 100644 --- a/source/glsl/generate.cpp +++ b/source/glsl/generate.cpp @@ -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); diff --git a/source/glsl/generate.h b/source/glsl/generate.h index a4ab7571..aba2c353 100644 --- a/source/glsl/generate.h +++ b/source/glsl/generate.h @@ -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. diff --git a/source/glsl/parser.cpp b/source/glsl/parser.cpp index d210cee9..615d75d4 100644 --- a/source/glsl/parser.cpp +++ b/source/glsl/parser.cpp @@ -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) diff --git a/source/glsl/preprocessor.cpp b/source/glsl/preprocessor.cpp index f2becd91..ca12ec61 100644 --- a/source/glsl/preprocessor.cpp +++ b/source/glsl/preprocessor.cpp @@ -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") diff --git a/source/glsl/spirv.cpp b/source/glsl/spirv.cpp index 85312bc4..b2eac3f0 100644 --- a/source/glsl/spirv.cpp +++ b/source/glsl/spirv.cpp @@ -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") diff --git a/source/glsl/spirvconstants.h b/source/glsl/spirvconstants.h index bd82becd..e3c245fb 100644 --- a/source/glsl/spirvconstants.h +++ b/source/glsl/spirvconstants.h @@ -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, diff --git a/source/glsl/syntax.cpp b/source/glsl/syntax.cpp index b805a95a..c37ad089 100644 --- a/source/glsl/syntax.cpp +++ b/source/glsl/syntax.cpp @@ -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]; } diff --git a/source/glsl/syntax.h b/source/glsl/syntax.h index 1f43eac3..ebe188e1 100644 --- a/source/glsl/syntax.h +++ b/source/glsl/syntax.h @@ -489,6 +489,8 @@ struct Stage { SHARED, VERTEX, + TESS_CONTROL, + TESS_EVAL, GEOMETRY, FRAGMENT, COMPUTE diff --git a/source/glsl/validate.cpp b/source/glsl/validate.cpp index 5a28fcb6..ce5a85b4 100644 --- a/source/glsl/validate.cpp +++ b/source/glsl/validate.cpp @@ -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(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()) -- 2.43.0