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
{
InterfaceGenerator().apply(stage);
resolve(stage, RESOLVE_BLOCKS|RESOLVE_TYPES|RESOLVE_VARIABLES);
+ LayoutDefaulter().apply(stage);
ArraySizer().apply(stage);
resolve(stage, RESOLVE_EXPRESSIONS);
}
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));
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;
}
+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);
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.
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)
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")
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")
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)
{
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;
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 };
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")
{
CAP_SHADER = 1,
CAP_GEOMETRY = 2,
+ CAP_TESSELLATION = 3,
CAP_STORAGE_IMAGE_MULTISAMPLE = 27,
CAP_IMAGE_CUBE_ARRAY = 34,
CAP_SAMPLED_1D = 43,
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,
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,
DECO_MATRIX_STRIDE = 7,
DECO_BUILTIN = 11,
DECO_FLAT = 14,
+ DECO_PATCH = 15,
DECO_CENTROID = 16,
DECO_LOCATION = 30,
DECO_BINDING = 33,
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,
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];
}
{
SHARED,
VERTEX,
+ TESS_CONTROL,
+ TESS_EVAL,
GEOMETRY,
FRAGMENT,
COMPUTE
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");
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");
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");
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())
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)
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())