diff options
author | Ian Romanick <ian.d.romanick@intel.com> | 2017-08-30 14:58:45 -0700 |
---|---|---|
committer | Ian Romanick <ian.d.romanick@intel.com> | 2018-03-29 14:16:12 -0700 |
commit | af86825523412492f140bf7b7fbc24d360238553 (patch) | |
tree | 130bbb260d11f97a1514d8f1b6ec651347f9624e | |
parent | fb322ba998f6512d7f1f544f2d96b37e598472ce (diff) |
glsl/spirv: Emit SPIR-V OpEntryPoint and OpExecutionMode for tessellation shaders
-rw-r--r-- | src/compiler/glsl/glsl_to_spirv.cpp | 89 | ||||
-rw-r--r-- | src/compiler/glsl/tests/emit_spirv_entry_point_test.cpp | 387 |
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)); +} |