diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index 206e858b23..837f422409 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -471,6 +471,9 @@ # define NV_PGRAPH_CONTROL_2_STENCIL_OP_V_INCR 7 # define NV_PGRAPH_CONTROL_2_STENCIL_OP_V_DECR 8 #define NV_PGRAPH_CONTROL_3 0x00001958 +# define NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX (1 << 0) +# define NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_LAST 0 +# define NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_FIRST 1 # define NV_PGRAPH_CONTROL_3_SHADEMODE (1 << 7) # define NV_PGRAPH_CONTROL_3_SHADEMODE_FLAT 0 # define NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH 1 @@ -1062,6 +1065,9 @@ # define NV097_SET_TEXGEN_VIEW_MODEL_INFINITE_VIEWER 1 # define NV097_SET_FOG_PLANE 0x000009D0 # define NV097_SET_SPECULAR_PARAMS 0x000009E0 +# define NV097_SET_PROVOKING_VERTEX 0x000009FC +# define NV097_SET_PROVOKING_VERTEX_LAST 0 +# define NV097_SET_PROVOKING_VERTEX_FIRST 1 # define NV097_SET_SCENE_AMBIENT_COLOR 0x00000A10 # define NV097_SET_VIEWPORT_OFFSET 0x00000A20 # define NV097_SET_POINT_PARAMS 0x00000A30 diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.c b/hw/xbox/nv2a/pgraph/glsl/geom.c index 2170696a08..dd5dc363f5 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.c +++ b/hw/xbox/nv2a/pgraph/glsl/geom.c @@ -38,6 +38,11 @@ void pgraph_glsl_set_geom_state(PGRAPHState *pg, GeomState *state) NV_PGRAPH_CONTROL_3_SHADEMODE) == NV_PGRAPH_CONTROL_3_SHADEMODE_SMOOTH; + state->first_vertex_is_provoking = + GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_CONTROL_3), + NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX) == + NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX_FIRST; + state->z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & NV_PGRAPH_CONTROL_0_Z_PERSPECTIVE_ENABLE; } @@ -83,67 +88,51 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) const char *layout_in = NULL; const char *layout_out = NULL; const char *body = NULL; + const char *provoking_index = "0"; + /* TODO: frontface/backface culling for polygon modes POLY_MODE_LINE and + * POLY_MODE_POINT. + * FIXME: OpenGL/Vulkan does not specify absolute vertex order when input + * is a triangle list, triangle strip or fan. Only vertex winding order + * is specified. Currently we assume input triangle vertex order follows + * the last provoking vertex convention. + */ switch (state->primitive_mode) { case PRIM_TYPE_POINTS: return NULL; case PRIM_TYPE_LINES: case PRIM_TYPE_LINE_LOOP: case PRIM_TYPE_LINE_STRIP: + provoking_index = state->first_vertex_is_provoking ? "0" : "1"; need_linez = true; layout_in = "layout(lines) in;\n"; layout_out = "layout(line_strip, max_vertices = 2) out;\n"; body = " mat4 pz = calc_linez(0, 1);\n" - " emit_vertex(0, 0, pz);\n" - " emit_vertex(1, 1, pz);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(1, pz);\n" " EndPrimitive();\n"; break; case PRIM_TYPE_TRIANGLES: - need_triz = true; - layout_in = "layout(triangles) in;\n"; - if (polygon_mode == POLY_MODE_FILL) { - layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; - body = " mat4 pz = calc_triz(0, 1, 2);\n" - " emit_vertex(0, 0, pz);\n" - " emit_vertex(1, 1, pz);\n" - " emit_vertex(2, 2, pz);\n" - " EndPrimitive();\n"; - } else if (polygon_mode == POLY_MODE_LINE) { - need_linez = true; - layout_out = "layout(line_strip, max_vertices = 4) out;\n"; - body = " float triMZ = calc_triz(0, 1, 2)[3].x;\n" - " mat4 pz1 = calc_linez(0, 1);\n" - " pz1[3].x = triMZ;\n" - " mat4 pz2 = calc_linez(1, 2);\n" - " pz2[3].x = triMZ;\n" - " mat4 pz3 = calc_linez(2, 0);\n" - " pz3[3].x = triMZ;\n" - " emit_vertex(0, 0, pz1);\n" - " emit_vertex(1, 0, pz1);\n" - " emit_vertex(2, 0, pz2);\n" - " emit_vertex(0, 0, pz3);\n" - " EndPrimitive();\n"; - } else { - assert(polygon_mode == POLY_MODE_POINT); - layout_out = "layout(points, max_vertices = 3) out;\n"; - body = " mat4 pz = calc_triz(0, 1, 2);\n" - " emit_vertex(0, 0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" - " EndPrimitive();\n" - " emit_vertex(1, 0, mat4(pz[1], pz[1], pz[1], pz[3]));\n" - " EndPrimitive();\n" - " emit_vertex(2, 0, mat4(pz[2], pz[2], pz[2], pz[3]));\n" - " EndPrimitive();\n"; - } - break; case PRIM_TYPE_TRIANGLE_STRIP: case PRIM_TYPE_TRIANGLE_FAN: + if (state->first_vertex_is_provoking) { + if (state->primitive_mode == PRIM_TYPE_TRIANGLE_STRIP) { + provoking_index = "gl_PrimitiveIDIn & 1"; + } else if (state->primitive_mode == PRIM_TYPE_TRIANGLE_FAN) { + provoking_index = "1"; + } else { + provoking_index = "0"; + } + } else { + provoking_index = "2"; + } need_triz = true; layout_in = "layout(triangles) in;\n"; if (polygon_mode == POLY_MODE_FILL) { layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; body = " mat4 pz = calc_triz(0, 1, 2);\n" - " emit_vertex(0, 0, pz);\n" - " emit_vertex(1, 1, pz);\n" - " emit_vertex(2, 2, pz);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(1, pz);\n" + " emit_vertex(2, pz);\n" " EndPrimitive();\n"; } else if (polygon_mode == POLY_MODE_LINE) { need_linez = true; @@ -155,31 +144,37 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) " pz2[3].x = triMZ;\n" " mat4 pz3 = calc_linez(2, 0);\n" " pz3[3].x = triMZ;\n" - " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 0, pz1);\n" - " }\n" - " emit_vertex(1, 0, pz1);\n" - " emit_vertex(2, 0, pz2);\n" - " emit_vertex(0, 0, pz3);\n" + " emit_vertex(0, pz1);\n" + " emit_vertex(1, pz1);\n" + " emit_vertex(2, pz2);\n" + " emit_vertex(0, pz3);\n" " EndPrimitive();\n"; } else { assert(polygon_mode == POLY_MODE_POINT); layout_out = "layout(points, max_vertices = 3) out;\n"; body = " mat4 pz = calc_triz(0, 1, 2);\n" - " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" - " EndPrimitive();\n" - " emit_vertex(1, 0, mat4(pz[1], pz[1], pz[1], pz[3]));\n" - " EndPrimitive();\n" - " }\n" - " emit_vertex(2, 0, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " emit_vertex(0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(1, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(2, mat4(pz[2], pz[2], pz[2], pz[3]));\n" " EndPrimitive();\n"; } break; case PRIM_TYPE_QUADS: + provoking_index = "3"; need_quadz = true; layout_in = "layout(lines_adjacency) in;\n"; - if (polygon_mode == POLY_MODE_LINE) { + if (polygon_mode == POLY_MODE_FILL) { + layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; + body = " mat4 pz, pz2;\n" + " calc_quadz(0, 1, 2, 3, pz, pz2);\n" + " emit_vertex(1, pz);\n" + " emit_vertex(2, pz2);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(3, pz2);\n" + " EndPrimitive();\n"; + } else if (polygon_mode == POLY_MODE_LINE) { need_linez = true; layout_out = "layout(line_strip, max_vertices = 5) out;\n"; body = " mat4 pz, pzs;\n" @@ -192,40 +187,42 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) " pz3[3].x = pzs[3].x;\n" " mat4 pz4 = calc_linez(3, 0);\n" " pz4[3].x = pzs[3].x;\n" - " emit_vertex(0, 3, pz1);\n" - " emit_vertex(1, 3, pz1);\n" - " emit_vertex(2, 3, pz2);\n" - " emit_vertex(3, 3, pz3);\n" - " emit_vertex(0, 3, pz4);\n" - " EndPrimitive();\n"; - } else if (polygon_mode == POLY_MODE_FILL) { - layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; - body = " mat4 pz, pz2;\n" - " calc_quadz(0, 1, 2, 3, pz, pz2);\n" - " emit_vertex(1, 3, pz);\n" - " emit_vertex(2, 3, pz2);\n" - " emit_vertex(0, 3, pz);\n" - " emit_vertex(3, 3, pz2);\n" + " emit_vertex(0, pz1);\n" + " emit_vertex(1, pz1);\n" + " emit_vertex(2, pz2);\n" + " emit_vertex(3, pz3);\n" + " emit_vertex(0, pz4);\n" " EndPrimitive();\n"; } else { assert(polygon_mode == POLY_MODE_POINT); layout_out = "layout(points, max_vertices = 4) out;\n"; body = " mat4 pz, pz2;\n" " calc_quadz(0, 1, 2, 3, pz, pz2);\n" - " emit_vertex(0, 3, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " emit_vertex(0, mat4(pz[0], pz[0], pz[0], pz[3]));\n" " EndPrimitive();\n" - " emit_vertex(1, 3, mat4(pz[1], pz[1], pz[1], pz[3]));\n" + " emit_vertex(1, mat4(pz[1], pz[1], pz[1], pz[3]));\n" " EndPrimitive();\n" - " emit_vertex(2, 3, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " emit_vertex(2, mat4(pz[2], pz[2], pz[2], pz[3]));\n" " EndPrimitive();\n" - " emit_vertex(3, 3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" + " emit_vertex(3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" " EndPrimitive();\n"; } break; case PRIM_TYPE_QUAD_STRIP: + provoking_index = "3"; need_quadz = true; layout_in = "layout(lines_adjacency) in;\n"; - if (polygon_mode == POLY_MODE_LINE) { + if (polygon_mode == POLY_MODE_FILL) { + layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; + body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" + " mat4 pz, pz2;\n" + " calc_quadz(2, 0, 1, 3, pz, pz2);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(1, pz2);\n" + " emit_vertex(2, pz);\n" + " emit_vertex(3, pz2);\n" + " EndPrimitive();\n"; + } else if (polygon_mode == POLY_MODE_LINE) { need_linez = true; layout_out = "layout(line_strip, max_vertices = 5) out;\n"; body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" @@ -239,23 +236,11 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) " pz3[3].x = pzs[3].x;\n" " mat4 pz4 = calc_linez(2, 0);\n" " pz4[3].x = pz[3].x;\n" - " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 3, pz1);\n" - " }\n" - " emit_vertex(1, 3, pz1);\n" - " emit_vertex(3, 3, pz2);\n" - " emit_vertex(2, 3, pz3);\n" - " emit_vertex(0, 3, pz4);\n" - " EndPrimitive();\n"; - } else if (polygon_mode == POLY_MODE_FILL) { - layout_out = "layout(triangle_strip, max_vertices = 4) out;\n"; - body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" - " mat4 pz, pz2;\n" - " calc_quadz(2, 0, 1, 3, pz, pz2);\n" - " emit_vertex(0, 3, pz);\n" - " emit_vertex(1, 3, pz2);\n" - " emit_vertex(2, 3, pz);\n" - " emit_vertex(3, 3, pz2);\n" + " emit_vertex(0, pz1);\n" + " emit_vertex(1, pz1);\n" + " emit_vertex(3, pz2);\n" + " emit_vertex(2, pz3);\n" + " emit_vertex(0, pz4);\n" " EndPrimitive();\n"; } else { assert(polygon_mode == POLY_MODE_POINT); @@ -263,39 +248,39 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) body = " if ((gl_PrimitiveIDIn & 1) != 0) { return; }\n" " mat4 pz, pz2;\n" " calc_quadz(2, 0, 1, 3, pz, pz2);\n" - " if (gl_PrimitiveIDIn == 0) {\n" - " emit_vertex(0, 3, mat4(pz[1], pz[1], pz[1], pz[3]));\n" - " EndPrimitive();\n" - " emit_vertex(1, 3, mat4(pz[2], pz[2], pz[2], pz[3]));\n" - " EndPrimitive();\n" - " }\n" - " emit_vertex(2, 3, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " emit_vertex(0, mat4(pz[1], pz[1], pz[1], pz[3]));\n" " EndPrimitive();\n" - " emit_vertex(3, 3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" + " emit_vertex(1, mat4(pz[2], pz[2], pz[2], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(2, mat4(pz[0], pz[0], pz[0], pz[3]));\n" + " EndPrimitive();\n" + " emit_vertex(3, mat4(pz2[2], pz2[2], pz2[2], pz2[3]));\n" " EndPrimitive();\n"; } break; case PRIM_TYPE_POLYGON: + provoking_index = "0"; if (polygon_mode == POLY_MODE_FILL) { need_triz = true; layout_in = "layout(triangles) in;\n"; layout_out = "layout(triangle_strip, max_vertices = 3) out;\n"; body = " mat4 pz = calc_triz(0, 1, 2);\n" - " emit_vertex(0, 0, pz);\n" - " emit_vertex(1, 0, pz);\n" - " emit_vertex(2, 0, pz);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(1, pz);\n" + " emit_vertex(2, pz);\n" " EndPrimitive();\n"; } else if (polygon_mode == POLY_MODE_LINE) { need_linez = true; - // FIXME: input here is lines and not triangles so we cannot - // calculate triangle plane slope. Also, the first vertex of the - // polygon is unavailable so flat shading provoking vertex is - // wrong. + /* FIXME: input here is lines and not triangles so we cannot + * calculate triangle plane slope. Also, the first vertex of the + * polygon is unavailable so flat shading provoking vertex is + * wrong. + */ layout_in = "layout(lines) in;\n"; layout_out = "layout(line_strip, max_vertices = 2) out;\n"; body = " mat4 pz = calc_linez(0, 1);\n" - " emit_vertex(0, 0, pz);\n" - " emit_vertex(1, 1, pz);\n" + " emit_vertex(0, pz);\n" + " emit_vertex(1, pz);\n" " EndPrimitive();\n"; } else { assert(false); @@ -326,49 +311,34 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) false, false, false); if (state->smooth_shading) { - mstring_append( - output, - "void emit_vertex(int index, int _unused, mat4 pz) {\n" - " gl_Position = gl_in[index].gl_Position;\n" - " gl_PointSize = gl_in[index].gl_PointSize;\n" - " vtxD0 = v_vtxD0[index];\n" - " vtxD1 = v_vtxD1[index];\n" - " vtxB0 = v_vtxB0[index];\n" - " vtxB1 = v_vtxB1[index];\n" - " vtxFog = v_vtxFog[index];\n" - " vtxT0 = v_vtxT0[index];\n" - " vtxT1 = v_vtxT1[index];\n" - " vtxT2 = v_vtxT2[index];\n" - " vtxT3 = v_vtxT3[index];\n" - " vtxPos0 = pz[0];\n" - " vtxPos1 = pz[1];\n" - " vtxPos2 = pz[2];\n" - " triMZ = (isnan(pz[3].x) || isinf(pz[3].x)) ? 0.0 : pz[3].x;\n" - " EmitVertex();\n" - "}\n"); - } else { - mstring_append( - output, - "void emit_vertex(int index, int provoking_index, mat4 pz) {\n" - " gl_Position = gl_in[index].gl_Position;\n" - " gl_PointSize = gl_in[index].gl_PointSize;\n" - " vtxD0 = v_vtxD0[provoking_index];\n" - " vtxD1 = v_vtxD1[provoking_index];\n" - " vtxB0 = v_vtxB0[provoking_index];\n" - " vtxB1 = v_vtxB1[provoking_index];\n" - " vtxFog = v_vtxFog[index];\n" - " vtxT0 = v_vtxT0[index];\n" - " vtxT1 = v_vtxT1[index];\n" - " vtxT2 = v_vtxT2[index];\n" - " vtxT3 = v_vtxT3[index];\n" - " vtxPos0 = pz[0];\n" - " vtxPos1 = pz[1];\n" - " vtxPos2 = pz[2];\n" - " triMZ = (isnan(pz[3].x) || isinf(pz[3].x)) ? 0.0 : pz[3].x;\n" - " EmitVertex();\n" - "}\n"); + provoking_index = "index"; } + mstring_append_fmt( + output, + "void emit_vertex(int index, mat4 pz) {\n" + " gl_Position = gl_in[index].gl_Position;\n" + " gl_PointSize = gl_in[index].gl_PointSize;\n" + " vtxD0 = v_vtxD0[%s];\n" + " vtxD1 = v_vtxD1[%s];\n" + " vtxB0 = v_vtxB0[%s];\n" + " vtxB1 = v_vtxB1[%s];\n" + " vtxFog = v_vtxFog[index];\n" + " vtxT0 = v_vtxT0[index];\n" + " vtxT1 = v_vtxT1[index];\n" + " vtxT2 = v_vtxT2[index];\n" + " vtxT3 = v_vtxT3[index];\n" + " vtxPos0 = pz[0];\n" + " vtxPos1 = pz[1];\n" + " vtxPos2 = pz[2];\n" + " triMZ = (isnan(pz[3].x) || isinf(pz[3].x)) ? 0.0 : pz[3].x;\n" + " EmitVertex();\n" + "}\n", + provoking_index, + provoking_index, + provoking_index, + provoking_index); + if (need_triz || need_quadz) { mstring_append( output, @@ -430,10 +400,12 @@ MString *pgraph_glsl_gen_geom(const GeomState *state, GenGeomGlslOptions opts) if (need_linez) { mstring_append( output, + // Calculate a third vertex by rotating 90 degrees so that triangle + // interpolation in fragment shader can be used as is for lines. "mat4 calc_linez(int i0, int i1) {\n" " vec2 delta = v_vtxPos[i1].xy - v_vtxPos[i0].xy;\n" " vec2 v2 = vec2(-delta.y, delta.x) + v_vtxPos[i0].xy;\n" - " return mat4(v_vtxPos[i0], v_vtxPos[i1], vec4(v2, v_vtxPos[i0].zw), vec4(0.0));\n" + " return mat4(v_vtxPos[i0], v_vtxPos[i1], v2, v_vtxPos[i0].zw, vec4(0.0));\n" "}\n"); } diff --git a/hw/xbox/nv2a/pgraph/glsl/geom.h b/hw/xbox/nv2a/pgraph/glsl/geom.h index 7dbd807345..55229d2e46 100644 --- a/hw/xbox/nv2a/pgraph/glsl/geom.h +++ b/hw/xbox/nv2a/pgraph/glsl/geom.h @@ -30,6 +30,7 @@ typedef struct { enum ShaderPolygonMode polygon_front_mode; enum ShaderPolygonMode polygon_back_mode; bool smooth_shading; + bool first_vertex_is_provoking; bool z_perspective; } GeomState; diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index d475f9b4b1..3f9e026103 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -68,6 +68,7 @@ DEF_METHOD(NV097, SET_STENCIL_OP_FAIL) DEF_METHOD(NV097, SET_STENCIL_OP_ZFAIL) DEF_METHOD(NV097, SET_STENCIL_OP_ZPASS) DEF_METHOD(NV097, SET_SHADE_MODE) +DEF_METHOD(NV097, SET_PROVOKING_VERTEX) DEF_METHOD(NV097, SET_POLYGON_OFFSET_SCALE_FACTOR) DEF_METHOD(NV097, SET_POLYGON_OFFSET_BIAS) DEF_METHOD(NV097, SET_FRONT_POLYGON_MODE) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index e3ecb09d12..2e93d77d9c 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -1534,6 +1534,13 @@ DEF_METHOD(NV097, SET_SHADE_MODE) } } +DEF_METHOD(NV097, SET_PROVOKING_VERTEX) +{ + assert((parameter & ~1) == 0); + PG_SET_MASK(NV_PGRAPH_CONTROL_3, NV_PGRAPH_CONTROL_3_PROVOKING_VERTEX, + parameter); +} + DEF_METHOD(NV097, SET_POLYGON_OFFSET_SCALE_FACTOR) { pgraph_reg_w(pg, NV_PGRAPH_ZOFFSETFACTOR, parameter); diff --git a/hw/xbox/nv2a/pgraph/vk/draw.c b/hw/xbox/nv2a/pgraph/vk/draw.c index c982d19bc4..e4bd562149 100644 --- a/hw/xbox/nv2a/pgraph/vk/draw.c +++ b/hw/xbox/nv2a/pgraph/vk/draw.c @@ -791,8 +791,8 @@ static void create_pipeline(PGRAPHState *pg) VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_state; if (r->provoking_vertex_extension_enabled) { - // TODO: remove use of provoking vertex extension since we just want - // the default last vertex convention always. + // Use last provoking vertex convention to match geometry shader + // assumption, because Vulkan default is first vertex convention. VkProvokingVertexModeEXT provoking_mode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;