summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Romanick <ian.d.romanick@intel.com>2017-08-30 14:58:45 -0700
committerIan Romanick <ian.d.romanick@intel.com>2018-03-29 14:16:12 -0700
commitaf86825523412492f140bf7b7fbc24d360238553 (patch)
tree130bbb260d11f97a1514d8f1b6ec651347f9624e
parentfb322ba998f6512d7f1f544f2d96b37e598472ce (diff)
glsl/spirv: Emit SPIR-V OpEntryPoint and OpExecutionMode for tessellation shaders
-rw-r--r--src/compiler/glsl/glsl_to_spirv.cpp89
-rw-r--r--src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp387
2 files changed, 476 insertions, 0 deletions
diff --git a/src/compiler/glsl/glsl_to_spirv.cpp b/src/compiler/glsl/glsl_to_spirv.cpp
index d8c04b8e6de..55f841ec4d0 100644
--- a/src/compiler/glsl/glsl_to_spirv.cpp
+++ b/src/compiler/glsl/glsl_to_spirv.cpp
@@ -1214,6 +1214,21 @@ _mesa_spirv_program::emit_constant(const ir_constant *constant)
return known_items.insert_pre_hashed(constant, result_id, hash);
}
+static SpvExecutionMode
+input_primitive_mode(GLenum prim)
+{
+ switch (prim) {
+ case GL_TRIANGLES:
+ return SpvExecutionModeTriangles;
+ case GL_QUADS:
+ return SpvExecutionModeQuads;
+ case GL_ISOLINES:
+ return SpvExecutionModeIsolines;
+ default:
+ unreachable("Invalid primitive mode.");
+ }
+}
+
void
_mesa_spirv_program::emit_entry_point(const struct shader_info *info,
const ir_function_signature *sig)
@@ -1249,6 +1264,80 @@ _mesa_spirv_program::emit_entry_point(const struct shader_info *info,
if (info->has_transform_feedback_varyings)
emit_SpvOpExecutionMode(&execution_modes, id, SpvExecutionModeXfb, NULL);
+
+ switch (info->stage) {
+ case MESA_SHADER_NONE:
+ unreachable("Unset shader stage");
+
+ case MESA_SHADER_VERTEX:
+ /* The only possible mode for a vertex shader is SpvExecutionModeXfb,
+ * and that was (potentially) set above.
+ */
+ break;
+
+ case MESA_SHADER_TESS_CTRL:
+ capabilities.enable(SpvCapabilityTessellation);
+
+ /* GLSL only requires only the "vertices" layout qualifier in a
+ * tessellation control shader, so we will only emit the matching
+ * SpvExecutionModeOutputVertices here.
+ */
+ emit_SpvOpExecutionMode(&execution_modes,
+ id,
+ SpvExecutionModeOutputVertices,
+ &info->tess.tcs_vertices_out);
+ break;
+
+ case MESA_SHADER_TESS_EVAL: {
+ capabilities.enable(SpvCapabilityTessellation);
+
+ /* The linker will have set spacing by now. */
+ assert(info->tess.spacing == TESS_SPACING_EQUAL ||
+ info->tess.spacing == TESS_SPACING_FRACTIONAL_ODD ||
+ info->tess.spacing == TESS_SPACING_FRACTIONAL_EVEN);
+
+ STATIC_ASSERT(unsigned(TESS_SPACING_EQUAL) == 1);
+ STATIC_ASSERT(unsigned(TESS_SPACING_FRACTIONAL_ODD) == 2);
+ STATIC_ASSERT(unsigned(TESS_SPACING_FRACTIONAL_EVEN) == 3);
+
+ static const SpvExecutionMode mode[] = {
+ SpvExecutionModeSpacingEqual,
+ SpvExecutionModeSpacingFractionalOdd,
+ SpvExecutionModeSpacingFractionalEven
+ };
+
+ emit_SpvOpExecutionMode(&execution_modes,
+ id,
+ mode[unsigned(info->tess.spacing) - 1],
+ NULL);
+
+ emit_SpvOpExecutionMode(&execution_modes,
+ id,
+ (info->tess.ccw ?
+ SpvExecutionModeVertexOrderCcw :
+ SpvExecutionModeVertexOrderCw),
+ NULL);
+
+ emit_SpvOpExecutionMode(&execution_modes,
+ id,
+ input_primitive_mode(info->tess.primitive_mode),
+ NULL);
+
+ if (info->tess.point_mode) {
+ emit_SpvOpExecutionMode(&execution_modes,
+ id,
+ SpvExecutionModePointMode,
+ NULL);
+ }
+
+ break;
+ }
+
+ case MESA_SHADER_GEOMETRY:
+ case MESA_SHADER_FRAGMENT:
+ case MESA_SHADER_COMPUTE:
+ break;
+ }
}
}
diff --git a/src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp b/src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp
index 99ef337e692..1a6871e867a 100644
--- a/src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp
+++ b/src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp
@@ -64,6 +64,32 @@ public:
}
};
+class validate_emit_tess_ctrl_entry_points : public validate_emit_entry_points {
+public:
+ validate_emit_tess_ctrl_entry_points()
+ : validate_emit_entry_points(MESA_SHADER_TESS_CTRL)
+ {
+ /* empty */
+ }
+
+ void check_tess(GLenum primitive_mode,
+ enum gl_tess_spacing spacing,
+ bool ccw);
+};
+
+class validate_emit_tess_eval_entry_points : public validate_emit_entry_points {
+public:
+ validate_emit_tess_eval_entry_points()
+ : validate_emit_entry_points(MESA_SHADER_TESS_EVAL)
+ {
+ /* empty */
+ }
+
+ void check_tess(GLenum primitive_mode,
+ enum gl_tess_spacing spacing,
+ bool ccw);
+};
+
void
validate_emit_entry_points::SetUp()
{
@@ -72,6 +98,15 @@ validate_emit_entry_points::SetUp()
main_sig = make_prototype(glsl_type::void_type,
"main",
NULL);
+
+ switch (stage) {
+ case MESA_SHADER_TESS_EVAL:
+ case MESA_SHADER_TESS_CTRL:
+ spv->capabilities.enable(SpvCapabilityTessellation);
+ break;
+ default:
+ break;
+ }
}
struct shader_info *
@@ -81,9 +116,49 @@ validate_emit_entry_points::allocate_shader_info()
info->stage = stage;
+ switch (stage) {
+ case MESA_SHADER_VERTEX:
+ break;
+
+ case MESA_SHADER_TESS_CTRL:
+ info->tess.tcs_vertices_out = 64;
+ info->tess.primitive_mode = GL_TRIANGLES;
+ info->tess.spacing = TESS_SPACING_EQUAL;
+ info->tess.ccw = false;
+ info->tess.point_mode = false;
+ break;
+
+ case MESA_SHADER_TESS_EVAL:
+ info->tess.tcs_vertices_out = unsigned(~0);
+ info->tess.primitive_mode = GL_TRIANGLES;
+ info->tess.spacing = TESS_SPACING_EQUAL;
+ info->tess.ccw = false;
+ info->tess.point_mode = false;
+ break;
+
+ default:
+ break;
+ }
return info;
}
+static uint32_t
+execution_mode_for_primitive(GLenum prim)
+{
+ switch (prim) {
+ case GL_TRIANGLES:
+ return 0x00000016;
+ case GL_QUADS:
+ return 0x00000018;
+ case GL_ISOLINES:
+ return 0x00000019;
+ default:
+ assert(!"Invalid primitive mode.");
+ }
+
+ return 0xffffffff;
+}
+
TEST_F(validate_emit_vertex_entry_points, simple)
{
struct shader_info *info = allocate_shader_info();
@@ -253,3 +328,315 @@ TEST_F(validate_emit_vertex_entry_points, invalid_from_fragment)
0x00000000
);
}
+
+void
+validate_emit_tess_ctrl_entry_points::check_tess(
+ GLenum primitive_mode, enum gl_tess_spacing spacing, bool ccw)
+{
+ struct shader_info *info = allocate_shader_info();
+
+ info->tess.primitive_mode = primitive_mode;
+ info->tess.spacing = spacing;
+ info->tess.ccw = ccw;
+
+ spv->emit_entry_point(info, main_sig);
+
+ /* Must manually end the entry point. It was left "open" so that the input
+ * and output interfaces could be added as the rest of the shader is
+ * processed.
+ */
+ end_SpvOpEntryPoint(&spv->entry_points);
+
+ const unsigned entry_point_id = spv->known_items.get_id(main_sig);
+
+ EXPECT_NE(0, entry_point_id);
+ EXPECT_EQ(entry_point_id, spv->stage_entry_point_id);
+
+ /* For the tessellation control shader, we expect the desktop GLSL rules
+ * which require only the "vertices" layout qualifier. In SPIR-V, this is
+ * implemented using SpvExecutionModeOutputVertices.
+ */
+ EXPECT_INSTRUCTIONS(spv->execution_modes,
+ 0x00040010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x0000001a, /* SpvExecutionModeOutputVertices */
+ 0x00000040, /* = 64 */
+ );
+
+ EXPECT_INSTRUCTIONS(spv->entry_points,
+ 0x0005000f, /* SpvOpEntryPoint */
+ exec_model, /* ExecutionModel */
+ entry_point_id, /* EntryPoint */
+ chars_to_word("main"), /* Name = "main" */
+ 0x00000000
+ );
+
+ EXPECT_TRUE(spv->capabilities.is_enabled(SpvCapabilityTessellation));
+}
+
+
+void
+validate_emit_tess_eval_entry_points::check_tess(
+ GLenum primitive_mode, enum gl_tess_spacing spacing, bool ccw)
+{
+ struct shader_info *info = allocate_shader_info();
+
+ info->tess.primitive_mode = primitive_mode;
+ info->tess.spacing = spacing;
+ info->tess.ccw = ccw;
+
+ spv->emit_entry_point(info, main_sig);
+
+ const uint32_t expected_prim = execution_mode_for_primitive(primitive_mode);
+ const uint32_t expected_winding = ccw ? 0x00000005 : 0x00000004;
+
+ uint32_t expected_spacing = uint32_t(~0);
+ switch (spacing) {
+ case TESS_SPACING_UNSPECIFIED:
+ break;
+ case TESS_SPACING_EQUAL:
+ expected_spacing = 0x00000001;
+ break;
+ case TESS_SPACING_FRACTIONAL_ODD:
+ expected_spacing = 0x00000003;
+ break;
+ case TESS_SPACING_FRACTIONAL_EVEN:
+ expected_spacing = 0x00000002;
+ break;
+ }
+
+ /* Must manually end the entry point. It was left "open" so that the input
+ * and output interfaces could be added as the rest of the shader is
+ * processed.
+ */
+ end_SpvOpEntryPoint(&spv->entry_points);
+
+ const unsigned entry_point_id = spv->known_items.get_id(main_sig);
+
+ EXPECT_NE(0, entry_point_id);
+ EXPECT_EQ(entry_point_id, spv->stage_entry_point_id);
+
+ /* This test is very rigid about the expected order of the
+ * SpvOpExecutionMode instructions. This makes the test simpler, but it
+ * means the test must be updated if the order things are emitted changes.
+ */
+ EXPECT_INSTRUCTIONS(spv->execution_modes,
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ expected_spacing,/* SpvExecutionModeSpacing(Equal|FractionalEven|FractionalOdd) */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ expected_winding,/* SpvExecutionModeVertexOrder(Ccw|Cw) */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ expected_prim /* SpvExecutionMode(Triangles|Quads|Isolines) */
+ );
+
+ EXPECT_INSTRUCTIONS(spv->entry_points,
+ 0x0005000f, /* SpvOpEntryPoint */
+ exec_model, /* ExecutionModel */
+ entry_point_id, /* EntryPoint */
+ chars_to_word("main"), /* Name = "main" */
+ 0x00000000
+ );
+
+ EXPECT_TRUE(spv->capabilities.is_enabled(SpvCapabilityTessellation));
+}
+
+#define TEST_TESS(name, thing) \
+TEST_F(validate_emit_tess_ctrl_entry_points, name) \
+{ \
+ thing ; \
+} \
+ \
+TEST_F(validate_emit_tess_eval_entry_points, name) \
+{ \
+ thing ; \
+}
+
+TEST_TESS(SpacingEqual_Triangles_CW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_EQUAL, false));
+TEST_TESS(SpacingEqual_Triangles_CCW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_EQUAL, true));
+TEST_TESS(SpacingFractionalEven_Triangles_CW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_FRACTIONAL_EVEN, false));
+TEST_TESS(SpacingFractionalEven_Triangles_CCW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_FRACTIONAL_EVEN, true));
+TEST_TESS(SpacingFractionalOdd_Triangles_CW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_FRACTIONAL_ODD, false));
+TEST_TESS(SpacingFractionalOdd_Triangles_CCW,
+ check_tess(GL_TRIANGLES, TESS_SPACING_FRACTIONAL_ODD, true));
+TEST_TESS(SpacingEqual_Quads_CW,
+ check_tess(GL_QUADS, TESS_SPACING_EQUAL, false));
+TEST_TESS(SpacingEqual_Quads_CCW,
+ check_tess(GL_QUADS, TESS_SPACING_EQUAL, true));
+TEST_TESS(SpacingFractionalEven_Quads_CW,
+ check_tess(GL_QUADS, TESS_SPACING_FRACTIONAL_EVEN, false));
+TEST_TESS(SpacingFractionalEven_Quads_CCW,
+ check_tess(GL_QUADS, TESS_SPACING_FRACTIONAL_EVEN, true));
+TEST_TESS(SpacingFractionalOdd_Quads_CW,
+ check_tess(GL_QUADS, TESS_SPACING_FRACTIONAL_ODD, false));
+TEST_TESS(SpacingFractionalOdd_Quads_CCW,
+ check_tess(GL_QUADS, TESS_SPACING_FRACTIONAL_ODD, true));
+TEST_TESS(SpacingEqual_Isolines_CW,
+ check_tess(GL_ISOLINES, TESS_SPACING_EQUAL, false));
+TEST_TESS(SpacingEqual_Isolines_CCW,
+ check_tess(GL_ISOLINES, TESS_SPACING_EQUAL, true));
+TEST_TESS(SpacingFractionalEven_Isolines_CW,
+ check_tess(GL_ISOLINES, TESS_SPACING_FRACTIONAL_EVEN, false));
+TEST_TESS(SpacingFractionalEven_Isolines_CCW,
+ check_tess(GL_ISOLINES, TESS_SPACING_FRACTIONAL_EVEN, true));
+TEST_TESS(SpacingFractionalOdd_Isolines_CW,
+ check_tess(GL_ISOLINES, TESS_SPACING_FRACTIONAL_ODD, false));
+TEST_TESS(SpacingFractionalOdd_Isolines_CCW,
+ check_tess(GL_ISOLINES, TESS_SPACING_FRACTIONAL_ODD, true));
+
+TEST_F(validate_emit_tess_ctrl_entry_points, point_mode)
+{
+ struct shader_info *info = allocate_shader_info();
+
+ info->tess.point_mode = true;
+
+ spv->emit_entry_point(info, main_sig);
+
+ /* Must manually end the entry point. It was left "open" so that the input
+ * and output interfaces could be added as the rest of the shader is
+ * processed.
+ */
+ end_SpvOpEntryPoint(&spv->entry_points);
+
+ const unsigned entry_point_id = spv->known_items.get_id(main_sig);
+
+ EXPECT_NE(0, entry_point_id);
+ EXPECT_EQ(entry_point_id, spv->stage_entry_point_id);
+
+ /* For the tessellation control shader, we expect the desktop GLSL rules
+ * which require only the "vertices" layout qualifier. In SPIR-V, this is
+ * implemented using SpvExecutionModeOutputVertices.
+ *
+ * This means there should be no SpvOpExecutionMode with
+ * SpvExecutionModePointMode for the control shader.
+ */
+ EXPECT_INSTRUCTIONS(spv->execution_modes,
+ 0x00040010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x0000001a, /* SpvExecutionModeOutputVertices */
+ 0x00000040, /* = 64 */
+ );
+
+ EXPECT_INSTRUCTIONS(spv->entry_points,
+ 0x0005000f, /* SpvOpEntryPoint */
+ exec_model, /* ExecutionModel */
+ entry_point_id, /* EntryPoint */
+ chars_to_word("main"), /* Name = "main" */
+ 0x00000000
+ );
+
+ EXPECT_TRUE(spv->capabilities.is_enabled(SpvCapabilityTessellation));
+}
+
+TEST_F(validate_emit_tess_eval_entry_points, point_mode)
+{
+ struct shader_info *info = allocate_shader_info();
+
+ info->tess.point_mode = true;
+
+ spv->emit_entry_point(info, main_sig);
+
+ /* Must manually end the entry point. It was left "open" so that the input
+ * and output interfaces could be added as the rest of the shader is
+ * processed.
+ */
+ end_SpvOpEntryPoint(&spv->entry_points);
+
+ const unsigned entry_point_id = spv->known_items.get_id(main_sig);
+
+ EXPECT_NE(0, entry_point_id);
+ EXPECT_EQ(entry_point_id, spv->stage_entry_point_id);
+
+ /* This test is very rigid about the expected order of the
+ * SpvOpExecutionMode instructions. This makes the test simpler, but it
+ * means the test must be updated if the order things are emitted changes.
+ */
+ EXPECT_INSTRUCTIONS(spv->execution_modes,
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000001, /* SpvExecutionModeSpacingEqual */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000004, /* SpvExecutionModeVertexOrderCw */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000016, /* SpvExecutionModeTriangles */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x0000000a /* SpvExecutionModePointMode */
+ );
+
+ EXPECT_INSTRUCTIONS(spv->entry_points,
+ 0x0005000f, /* SpvOpEntryPoint */
+ exec_model, /* ExecutionModel */
+ entry_point_id, /* EntryPoint */
+ chars_to_word("main"), /* Name = "main" */
+ 0x00000000
+ );
+
+ EXPECT_TRUE(spv->capabilities.is_enabled(SpvCapabilityTessellation));
+}
+
+TEST_F(validate_emit_tess_eval_entry_points, transform_feedback)
+{
+ struct shader_info *info = allocate_shader_info();
+
+ info->has_transform_feedback_varyings = true;
+
+ spv->emit_entry_point(info, main_sig);
+
+ /* Must manually end the entry point. It was left "open" so that the input
+ * and output interfaces could be added as the rest of the shader is
+ * processed.
+ */
+ end_SpvOpEntryPoint(&spv->entry_points);
+
+ const unsigned entry_point_id = spv->known_items.get_id(main_sig);
+
+ EXPECT_NE(0, entry_point_id);
+ EXPECT_EQ(entry_point_id, spv->stage_entry_point_id);
+
+ /* This test is very rigid about the expected order of the
+ * SpvOpExecutionMode instructions. This makes the test simpler, but it
+ * means the test must be updated if the order things are emitted changes.
+ */
+ EXPECT_INSTRUCTIONS(spv->execution_modes,
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x0000000b, /* Mode = SpvExecutionModeXfb*/
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000001, /* SpvExecutionModeSpacingEqual */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000004, /* SpvExecutionModeVertexOrderCw */
+
+ 0x00030010, /* SpvOpExecutionMode */
+ entry_point_id, /* EntryPoint */
+ 0x00000016 /* SpvExecutionModeTriangles */
+ );
+
+ EXPECT_INSTRUCTIONS(spv->entry_points,
+ 0x0005000f, /* SpvOpEntryPoint */
+ exec_model, /* ExecutionModel */
+ entry_point_id, /* EntryPoint */
+ chars_to_word("main"), /* Name = "main" */
+ 0x00000000
+ );
+
+ EXPECT_TRUE(spv->capabilities.is_enabled(SpvCapabilityTessellation));
+}